Вкратце:
- cftotalcontrol — модуль создания среды специальной учётной записи с SSH доступом к другим узлам инфраструктуры и выполнения массовых рутинных задач параллельно.
- Отвлечённая от реализации теория организации безопасного и контролируемого доступа к инфраструктуре.
- Описывается практическое применение конкретного решения
Тематический цикл:
- Часть I: сеть и сетевой фильтр (cfnetwork + cffirehol)
- Часть II: доступ и стандартное окружение (cfauth + cfsystem)
- Часть III: установка Puppet Server (cfpuppetserver)
- Часть IV: централизованное управление (cftotalcontrol)
Концепция организации доступа к инфраструктуре
Безусловно, данная статья не является исчерпывающих руководством. Тем не менее, для наглядности рассмотрим классический пример админского доступа к серверам, в котором у каждого админа есть свой приватный ключ и прямой доступ на любой подконтрольный ему сервер:
У такого подхода есть определённые недостатки:
- Необходимость прописывать публичный ключ каждого админа на каждом сервере.
- Прописать новые ключи через Puppet не проблема, но требуется фактическое изменение конфигурации всех доступных систем.
- Необходимость организовать сквозной доступ к каждой системе из любого места нахождения админа.
- Зачастую для этого опасно используют SSH Agent Forwarding.
- Иногда можно встретить совершенно не гибкое решение через DNAT.
- Прописывание всех админских IP и/или открытие портов по стуку на каждой системе тоже несколько сомнительное решение.
- Каждому админу приходится вручную содержать общий или собственный SSH config и набор скриптов для рутинный задач, а также вовремя их обновлять. Это гарантированно ведёт к ошибках.
- С ростом количества систем аудит организации доступа в инфраструктуре превращается в нетривиальную задачу.
- Непосредственно централизованное управление (дирижирование) не решено.
- Безусловно существует разнообразные решения на основе событийной модели, но их безопасность вызывает некоторые сомнения. У самого Puppet для этого есть MCollective.
Для сравнения рассмотрим доступ с условным администратором инфраструктуры в виде доменной группы:
Данный подход называют по разному, но так или иначе он уже давно существует в интегрированных коммерческих решениях. Обычно это реализовано через некую доменную группу привилегированных пользователей, которая имеет доступ ко всем или части систем инфраструктуры. У такого подхода есть некоторые (2, 3 и 5) недостатки предыдущего плюс очевидный нюанс — необходимость в постоянной доступности сервисов аутентификации и авторизации (конечно, не без кэширования). К слову, не составляет большого труда организовать нечто подобное с помощью PAM и LDAP.
Итак, мы быстро подобрались к сути реализованной концепции доступа в
cftotalcontrol
:По сути данный подход объединяет два предыдущих:
- Для доступа используется исключительно SSH без каких-либо дополнительных сервисов. Puppet используется только в момент распространения ключей авторизации.
- Вводится управляющий пользователь — специальная учётная запись на особо безопасных узлах инфраструктуры, к которой подключаются админы. Абстрактно такая учётная запись представляет собой группу админов.
- Система сама настраивает промежуточные узлы и SSH клиент управляющего пользователя. В системах прописано минимальное количество ключей авторизации, что хорошо масштабируется с ростом и количества узлом, и количества админов.
- На рабочем месте админа не требуется каких-либо конфигураций, кроме подключения к управляющей учётной записи.
- Полностью контролируемая учётная запись позволяет легко наладить аудит действий админов с ведением журнала. Особенно полезно для низко привилегированных групп.
- Имеются готовые решения для дирижирования большим количеством систем:
- можно запустить команду на всех доступных узлах.
- можно определить узлы в группы статически или динамически по запросу в PuppetDB.
- можно запускать команды последовательно в интерактивном режиме.
- можно запускать команды параллельно с ограничение максимального количества параллельных соединений и получать вывод команды по завершению.
- Для рутинных задач, создаются команды по шаблону для каждого узла, для группы узлов и для всех узлов. В список стандартных команд входят:
- принудительное развёртывание Puppet.
- обновление мета-информации из репозиториев пакетов.
- обновление системы.
- удаление старых неиспользуемых пакетов.
- любые произвольные команды, задаваемые в конфигурации.
- Есть возможность создать низко привилегированных управляющих пользователей (Scoped Control Users) вместо привилегированных "тотального" контроля (Total Control Users).
- такие пользователи будут иметь доступ только к тем системам, которые явно помечены как относящиеся к данной сфере (Control Scope).
Установка
Подразумевается, что инфраструктура построена на ранее рассмотренных модулях
cfnetwork
, cfauth
и cfpuppetserver
, но cftotalcontrol
будет прекрасно жить без конфликтов и в смешанной среде.Во-первых, чтобы спать спокойно лучше всё же иметь хотя бы один SSH ключ, который будет прописан на всех системах через модуль
cfauth
, но который не будет использоваться для "бытовых" нужд. Разумеется, не стоит забывать про доступ к железу через IPMI и к виртуалкам через serial/GUI консоль на экстренный случай.Во-вторых, достаточно посмотреть на пример и:
- добавить модуль
cftotalcontrol
вPuppetfile
mod 'codingfuture/cftotalcontrol'
- в список
classes
всех узлов (файлcommon.yaml
)
classes: # Обозначить узел, как подлежащий "тотальному" управлению - cftotalcontrol::auth
- в список
classes
конфигурации узла с управляющим пользователем (далее "управляющий узел")
classes: # Создать управляющего пользователя - cftotalcontrol
- требуется запустить развёртывание Puppet (
/opt/puppetlabs/bin/puppet agent --test
) в следующем порядке:
- Развернуть Puppet на управляющем узле — создать управляющего пользователя и его среду.
- Развернуть Puppet на всех остальных узлах — это зарегистрирует систему в PuppetDB для автоматической генерации конфигурации управляющего пользователя.
- Зайти под управляющего пользователя и сгенерировать приватный ключ (будет предложено автоматически). После генерации будет автоматически вызвано развёртывание Puppet чтобы экспортировать публичный ключ в PuppetDB.
- Развернуть Puppet на всех системах ещё раз — везде пропишется публичный ключ управляющего пользователя, а у самого пользователя создаст конфигурацию с доступом ко всем узлам.
- Такая последовательность действий требуется только при изначальной установке. Далее, обновление ключей автоматизировано при генерации нового.
После этих нехитрых манипуляций, на узлах с классом
cftotalcontrol
будет создан пользователь cftcuser
со специально заточенной средой Bash и конфигурацией SSH клиента. Настоятельно рекомендуется изменить имя пользователя на нестандартной через параметр cftotalcontrol::control_user
.Все стандартные админские ключи из
cfauth
будут прописаны и данному пользователю, но для организации идеологически правильного доступа, админские ключи должны быть прописаны только в cftotalcontrol::ssh_auth_keys
. Разумеется, сначала следует проверить, что всё работает и доступ есть ко всем серверам.Прокси-доступ по SSH
В крупной инфраструктуре, практически всегда во внешний мир смотрит лишь малая часть систем и через них требуется организовать доступ к остальным системам.
В SSH клиенте, это легко реализуется через опцию
ProxyCommand
со значением ssh -W target:port proxyhost
. Такой подход более безопасен, чем использование SSH Agent Forwarding, позволяющего утащить приватный ключ админа на удалённой системе. В принципе, знание этих технических деталей необязательно для использование модуля cftotalcontrol
, т.к. вся необходимая конфигурация генерируется на основе параметра cftotalcontrol::pool_proxy
.Разумеется, перечисление прокси-узла для каждого целевого узла не только не эффективно, но и обязательно ведёт к частым ошибкам при добавлении новых систем. Вместо этого, для "навигации" используются факты
cf_location
и cf_location_pool
, описанные в предыдущих частях. Обычно эти факты задаются при изначальной установке скриптом инициализации клиента cf_gen_puppet_client_init
, но могут быть изменены через параметры модуля cfsystem
.При генерации конфига SSH клиента, прокси узлы ищутся в следующем порядке:
$cftotalcontrol::pool_proxy["$cf_location/$cf_location_pool"]
$cftotalcontrol::pool_proxy["$cf_location"]
$cftotalcontrol::pool_proxy["$certname"]
— да, для исключительных случаев была оставлена лазейка указать прокси-узел на основе полного имени целевого узла.
Разумеется, всё это дело интегрировано с
cfnetwork
и без лишних телодвижений добавляет всё, что нужно в конфигурацию сетевого фильтра.Группы узлов
Обычно, специфичные однотипные задачи требуется запустить только на узлах с определёнными признаками (ОС, набор сервисов, роль и т.п.). Чтобы не городить свой огород по сегрегации узлов, это легко сделать через параметр
cftotalcontrol::host_groups
, который представляет собой ассоциативный массив, где ключ — это название группы, а значение может быть жёстко заданным списком узлом или строкой в виде запроса Puppet DB, о котором есть возможность почитать в описании модуля puppetdbquery.Пример:
cftotalcontrol::host_groups:
puppetserver: "Package['puppetserver']"
infra: "cf_location_pool = 'infra'"
custom:
- 'web.example.com'
- 'db.example.com'
Стандартные рутинные команды
Должно быть понятно без лишних объяснений:
cftotalcontrol::standard_commands:
helloworld: 'echo "Hello world!"'
gethostname: 'hostname --fqdn'
Знакомство со средой централизованного управления
Вот тут и начинается виртуальный пост админа. При первом заходе, система любезно предложит сгенерировать приватный ключ, а при последующих запустит SSH Agent и добавит в него приватный ключ, попутно спрашивая его пароль.
А вот чтобы жизнь не казалась мёдом, система будет напоминать о необходимости обновить приватный ключ, если он старше
cftotalcontrol::ssh_old_key_days = 180
дней. Если даже не заходить, то же самое будет делать cron, гневно высылая ежедневные письма. Сгенерировать новый ключ можно командой cftc_gen_key
, которая ещё и автоматически установит его на всех контролируемых узлах, не требуя иных действий. Если что-то идёт не так, то старый ключ всегда можно найти в каталоге ~/.ssh/
с расширением в виде UNIX timestamp в секундах на момент генерации нового ключа. Такое может произойти из-за ошибки соединения, конфигурации Puppet или банального offline контролируемой системы.Вообще, под капотом творится следующее:
~/.bash_aliases
— обязательно присутствует строка с подключением~/.cftotalcontrol_aliases
~/.cftotalcontrol_aliases
— вся магия Bash среды~/.ssh/cftotalcontrol_config
— специальный конфиг SSH с правильными пользователями, портами иProxyCommand
. Стоит ли говорить, что данные тоже вытащены из PuppetDB.~/.ssh/cftchostsall
— список всех подконтрольных узлов.~/.ssh/cftchosts_${grp}
— список подконтрольных узлов конкретной группы.~/.ssh/cftc_id_${ssh_key_type}
— текущий приватный ключ.~/.ssh/cftc_id_${ssh_key_type}.${backup_timestamp}
— старые приватные ключи, которые в нормальном случае нигде не прописаны.
Служебные команды Bash среды:
cftc_ssh
— правильный запуск SSH со всеми необходимыми параметрами.cftc_scp
— правильный запуск SCP со всеми необходимыми параметрами.cftc_gen_key
— ручная перегенерация приватного ключа (вызывается автоматически при заходе в отсутствии ключа).cftc_add_key
— запуск SSH Agent'а и добавление приватного ключа (вызывается автоматически при заходе).cftc_check_old_key
— ручная проверка старого ключа (вызывается автоматически при заходе и ежедневно по cron'у).
Непосредственно работа с узлами:
ssh_${hostname} [$cmd]
— зайти на конкретный узел или запустить на нём произвольную команду в интерактивном режиме. Примечание: точки заменяются подчёркиванием в${hostname}
ssh_${hostname}_{stdcmd} [args]
— запустить одну из стандартных команд на конкретном узле.ssh_masscmd {cmd}
— последовательно запустить{cmd}
на всех узлах в интерактивном режиме.ssh_mass_{stdcmd} [args]
— последовательно запустить одну из стандартных команд на всех узлах в интерактивном режим.pssh_masscmd {cmd}
— параллельно запустить{cmd}
на всех узлах.pssh_mass_{stdcmd} [args]
— параллельно запустить одну из стандартных команд на всех узлах.sshgrp_{group}_*
иpsshgrp_{group}_*
— то же, что и предыдущие "массовые", но для ограниченной именованной группы узлов.
Значимые переменные среды:
PSSH_COUNT=$cftotalcontrol::parallel
— максимальное количество параллельных вызовов. Стоит помнить, что на прокси-узлы пойдёт большая нагрузка и соединения могут быть зарублены по опции MaxStartups. К сожалению, подход с ControlMaster не оправдывает себя с большим количеством соединений, т.к. мультиплексирование работает достаточно посредственно — он больше подходит для ускорения работы последовательных вызовов.PSSH_OPTS=-i
— дополнительные параметры дляparallel-ssh
(pssh)SSH_OPTS=
— дополнительные параметры дляssh
SCP_OPTS=
— дополнительные параметры дляscp
Стандартные команды — те же, что и добавлены в
cfauth
для вызова через sudo
без пароля:aptupdate
—sudo /usr/bin/apt-get update
aptdistupgrade
—sudo DEBIAN_FRONTEND=noninteractive /usr/bin/apt-get dist-upgrade -o Dpkg::Options::="--force-confold" -qf
aptautoremove
—sudo DEBIAN_FRONTEND=noninteractive /usr/bin/apt-get autoremove
puppetdeploy
—sudo /opt/puppetlabs/puppet/bin/puppet agent --test
- Пример использования:
pssh_mass_aptupdate
,psshgrp_grpname_aptdistupgrade -sy
,ssh_host_example_com_puppetdeploy
Создание админов с ограниченным доступом
Рано или поздно, определённые админские полномочия требуется давать людям. чьей зоной ответственности не является администрирование всей инфраструктуры. В число таких входят DevOps, DBA, Release Manager и прочие. Некоторые средства кластеризации также полагаются на SSH доступ к узлам-собратьям, который удобно настроить этим же модулем, включая автоматическую генерацию приватного ключа без пароля.
Если коротко, то стандартный путь создания непривилегированных админов лежит через параметр
cftotalcontrol::extra_users
, который правильно создаёт ресурсы типа cftotalcontrol::admin
. Особые отличия от пользователя "тотального" управления:- Имя пользователя — это и имя соответствующей сферы управления.
- На всех промежуточных узлах будет создан пользователь с таким же именем, но постфиксом "_proxy", которому будет доступно только прокидывание соединения и принудительное развёртывания Puppet (используется при обновлении ключа).
Остаётся только вопрос как обозначить целевой узел. Это делается через параметр
cftotalcontrol::auth::control_scope
. На один и тот же узел можно повесить сразу несколько сфер. Пример:cftotalcontrol::auth::control_scope:
- web
- devops
Документация по модулю cftotalcontrol
класс cftotalcontrol
pool_proxy = {}
. Пары ключ => "имя.прокси.узла". Формат ключей:
- "${cf_location}/${cf_location_pool}"
- "${cf_location}"
- "${certname}"
control_user = 'cftcuser'
— имя управляющего пользователя.control_home = undef
— домашняя папка. По умолчанию:/home/$control_user
.host_groups = {}
— определение групп узлов для команд типа(p)sshgrp_*
. Ключ — название группы. Значение:
- массив — статичный список узлом в группе
- string — запрос Puppet DB для динамичного списка
parallel = 10
— количество параллельных вызовов SSH.standard_commands = {}
— стандартные рутинные команды для добавления в список.ssh_key_type = 'rsa'
— типа приватного ключа. Стоит также уделить внимание новому типу 'ed25519'.ssh_key_bits = 4096
— длина приватного ключа, игнорируется для ed25519.autogen_ssh_key = false
— автоматически сгенерировать приватный ключ без пароля (не стоит для "тотального" контроля).ssh_old_key_days = 180
— возраст приватного ключа после которого, система начинает плакаться в cron'е и при заходе в учётную запись.ssh_auth_keys = undef
— дополнительные ключи доступа к пользователю помимо тех, что заданы вcfauth
extra_users = undef
— дополнительный пользователи ограниченного доступа к инфраструктуреИмяДоступа => параметры cftotalcontrol::admin
класс cftotalcontrol::auth
control_scope = []
— строка или массив строк с именами зон ограниченного доступа, которые применимы к данной системе
тип cftotalcontrol::admin
Обычно инициализируется через
cftotalcontrol::extra_users
. Все параметры такие же, как у cftotalcontrol
, за исключением самого extra_users
.control_scope = undef
— название зоны ограниченного доступа, если применимо. При создании черезextra_users
соответствует названию пользователя. Если не задана, то создаётся пользователь с неограниченным доступом.