Вот есть у вас сотня коммутаторов или маршрутизаторов. Это много или мало?

Ну вроде как мало. А если надо на всех разом нового сотрудника добавить? А потом удалить уволившегося? А потом поротировать скомпрометированные пароли и ключи?

И тут приходит служба безопасности, которая, во‑первых, хочет централизованно контролировать, у кого какие доступы, во‑вторых, актуальны ли они на железках, в‑третьих, ещё и смотреть, кто что когда запускал, да ещё и разрешать или запрещать это делать («Просто продолжай, не останавливайся» © СИБ).

Ну вот совсем уже и немало. Чувствуете, чем это пахнет? Даааа, TACACS‑ом:)

Сегодня мы разберём на примере Yandex Cloud, как настроить аутентификацию и авторизацию на сетевом оборудовании на основе TACACS, сделать работу сервиса отказоустойчивой, обеспечить себе запасной ход на случай глобальных проблем и осчастливить безопасников.

С чего всё началось

Да как и у многих, честно говоря, с локальной базы пользователей. Сначала нас было четверо, потом шестеро, а потом 15. И плюс‑минус раз в несколько месяцев нам приходилось обновлять базу пользователей.

На нескольких сотнях коробок.

И хорошо бы была уверенная автоматика, которой ты поставил джобу — и она надёжно и самостоятельно всё раскатила. Ясное дело, мы делали это не руками. Но процесс занимал время, был несовершенен. А ещё постоянно оставались какие‑то единичные коробки, на которые просто‑напросто забыли докатить.

В итоге иной раз на лабных коммутаторах мы находили прелюбопытных старожилов.

А что-то вообще было? Да — Bastion

Сервис Bastion — это специальные машинки, встающие вразрез SSH‑сессии. Когда сотрудник Яндекса хочет подключиться на продовый хост Yandex Cloud, сначала устанавливается сессия до Bastion, а сам Bastion уже подключается на целевой хост. То есть пользователь может зайти на бастионный хост, а тот — на целевые. Но не напрямую от пользователя к целевым.

В целом Bastion — это обычный SSH‑jump‑хост, но который ещё делает MITM.

Ну, почти… Давайте разберёмся.

В случае обычного jump‑хоста вы просите его установить TCP‑туннель (в рамках SSH‑соединения с jump‑хостом) до целевого хоста. Jump‑хост в этой ситуации выступает в роли TCP‑proxy: соединяется с целевым хостом и шлёт байтики от вашего клиента до целевого хоста и обратно.

И это может быть любой протокол поверх TCP, необязательно SSH. Например, вы можете соединиться с PostgreSQL или кластером K8s. SSH на целевой хост — частный случай. Для этого в клиент SSH даже добавили специальный флаг для SSH‑over‑SSH: если вы напишете ssh -J <jump-host> <target-host>, то ваш клиент как раз попросит jump‑хост установить SSH‑соединение (просто TCP‑соединение по 22 порту) с целевым хостом и дальше попробует залогиниться на него.

Отличие Bastion от обычных jump‑хостов как раз в том, что он влезает в это TCP‑соединение как MITM: представляется SSH‑сервером целевого хоста для пользователя и при этом сам устанавливает клиентское SSH‑соединение с целевым хостом.

Таким образом, Bastion:

  • видит весь трафик SSH‑сессии в незашифрованном виде;

  • может подменять аутентификацию до целевого хоста своей.

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

Подмена аутентификации интереснее, потому что:

  • позволяет гарантировать, что пользователь не обойдёт Bastion;

  • позволяет клиенту использовать один способ аутентификации, но при этом Bastion приходится ходить на различные устройства, которые поддерживают разные способы аутентификации.

Чтобы обеспечить невозможность обхода Bastion, мы не только используем фаерволы, но и конфигурируем целевые хосты так, что они не принимают пользовательские креды, а принимают креды Bastion. При этом для разных устройств и систем креды могут отличаться (кроме пользовательских, которые проверяются на Bastion). Для обычных Linux‑машинок это SSH‑сертификаты, выписанные на приватном ключе Bastion. Таким образом, единственное место, куда пользователь может залогиниться со своими кредами, — это собственно Bastion.

При этом мы в Яндексе начинаем применять Bastion не только для SSH

..., но и для соединений с базами данных и K8s. Пользователь также устанавливает соединение с Bastion, а дальше при попытке пробросить TCP-туннель до конечного сервиса Bastion делает MITM, только уже в терминах протокола базы данных (например, Postgres или MongoDB) или Kubernetes.

То есть при инциденте безопасности всегда можно ретроспективно проиграть интересующую сессию и даже в реальном времени разорвать её через интерфейс Bastion. И вот как это выглядит:

Для работы привычных средств и автоматики специальным образом настраивается SSH‑агент для подключения через Bastion.

Про сеть

Обычно про неё забывают или опасливо молчат, потому что это другой мир. Там не Linux, проприетарные операционные системы, и вообще это сложно (ну правда сложно).

Однако с точки зрения безопасности сеть — это такая же точка приложения атаки, как и хосты. Даже, возможно, более уязвимая — с её telnet‑ами, парольной аутентификацией, неротируемыми ключами.

В общем, сетевое оборудование тут не исключение. Ровно такое же требование предъявлялось и к нему — все операции должны проходить только через Bastion.

Но есть нюанс.

Нюансище

В случае с Linux‑хостами авторизация Bastion на конечном хосте происходит по SSH‑сертификатам с общеизвестным CA (приватный ключ от этого CA есть только у Bastion), одинаковым для всех. Поэтому Bastion легко выступает в качестве джамп‑хоста, хранить на серверах ключи пользователей не нужно.

А вот сетевое оборудование в массе своей поддерживает ключи RSA/OpenSSH и знать не знает ничего про сертификаты. И это фактически означает, что нужно:

  • либо хранить на Bastion приватный ключ пользователя — это фу‑фу‑фу;

  • либо придумывать новую пару ключей под каждого пользователя: вместо одной проблемы на каждого мы получаем суперпозицию двух;

  • либо завести одного пользователя  bastion_user и всех и каждого имперсонировать в него, но тогда в чём вообще смысл?

Есть ещё вариант с пробросом агента (agent forwarding) на Bastion. Тогда на нём в локальном SSH‑агенте материализуются ключи пользователя, и можно через SSH на Bastion попросить его выполнить SSH на целевой хост.

Но это небезопасная штука: если какой‑то ушлый админ джамп‑хоста перехватит вашего агента, он может имперсонироваться в вас.

И мы некоторое время пользовались этим ещё на тот момент поддерживаемым лайфхаком. Но долго так продолжаться не могло.

Ну а самое‑то главное — это всего лишь помогает нам ходить через Bastion, а локальная база пользователей всё равно нужна. То есть мы уважили СИБ, но жизнь себе только усложнили.

Ну и в целом непонятно, почему сетевое оборудование так сильно отстаёт от индустрии. Авторизации через сертификаты исполнилось уже 14 лет. Но большинство вендоров в свои ОС так и не принесли этот механизм, словно отрицают реальность, даже не подозревая об их существовании.

Отступление: почему аутентификация по сертификатам — это клёво

Сертификаты - это просто

Неоспорим ли факт, что аутентификация по публичным ключам сильно удобнее и безопаснее, чем по паролям? Неоспорим! Поэтому мы долгое время предпочитали паролям публичные ключи.

А что не так с ключами? Там есть четыре фундаментальные проблемы.

Проблема первая. Вот я устроился в компанию: как и когда мой публичный ключ окажется на всех нужных мне машинах?

Проблема вторая. TOFU (Trust On First Use) клиент при первом подключении просто доверяет, что на той стороне именно тот сервер, за который тот пытается себя выдать.

This highlights a fundamental weakness of SSH key authentication — it promotes the ‘trust on first use’ (TOFU) anti‑pattern. And much like the food, TOFU is incredibly overrated.

И даже если к уже существующему адресу у вас внезапно выдаётся ошибка об изменившемся отпечатке, мы что обычно делаем? Идём к администраторам? Да ведь?

The authenticity of host 'some_cocis' can't be established.
RSA key fingerprint is SHA256:kfcwi9X8T4nMRw1OM0xDXETGcqjU26/zbM+KqNB6CKw.
Are you sure you want to continue connecting (yes/no)?

Проблема третья. Если кто‑то завладеет вашим приватным ключом, то он без проблем выдаст себя за вас. И ни у кого не возникнет вопросов. Особенно если мы говорим про каких‑то роботных пользователей, доступ к секретам которых есть обычно у нескольких людей.

От этого может помочь защита ключа паролем. Но это настолько неудобно для людей и неработоспособно для роботов, что мы ставим памятник и даём медальку тем, кто делает passphrase.

Проблема четвёртая. Решить этот вопрос можно регулярной ротацией ключей, чего по наивности душевной требуют и безопасники. Но если вам знакомо слово «масштаб», вы понимаете, с какими сложностями это сопряжено. И это общепризнанная проблема: ротация ключей — это болезненный процесс, который мало кто реализует.

Сертификаты отвечают на все эти сложности. Сертификат вообще, по сути, это публичный ключ, набор метаданных типа даты создания, времени жизни, username и подпись всего этого добра приватным ключом certificate authority.

В случае сертификатов:

  • сервер аутентифицирует клиента;

  • клиент аутентифицирует сервер;

  • сертификаты клиента и сервера живут короткое время, у них есть username и дополнительные метаданные, поэтому уволенный сотрудник не сможет ими воспользоваться, и мы меньше беспокоимся о краже;

  • поскольку есть рутовый сертификат, вопросы доверия вызывает только он;

  • сертификаты просто работают без необходимости ввода каких‑либо паролей;

  • ротация сертификатов — это их естественное свойство, а не мучительный процесс;

  • дополнительные метаданные оседают в логах Bastion, jump‑хоста, целевого хоста и сильно помогают потом с разбором инцидентов безопасности: кто точно куда заходил.

Но есть у сертификатов одна неприятная вещь, роднящая их с использованием асимметричных ключей: всё равно нужно где‑то иметь приватный ключ от личного сертификата. Увы, чудес не бывает.

Сколько ещё сетевые вендоры будут их отрицать? И что нам делать с необходимостью использовать Bastion, но без возможности пробрасывать агента?

И тут на помощь спешит…

TACACS

Разберёмся, как работает этот уже совсем немолодой протокол.

Созданный в 1984 году компанией BBN Technologies протокол для управления ARPANET был сперва опубликован как рекомендация в RFC 1492 (A n Access Control Protocol, sometimes called TACACS). Далее в 1996 году TACACS+ увидел свет в роли draft‑а А четыре года назад обновлён в RFC 8907 (The Terminal Access Controller Access‑Control System Plus (TACACS+) Protocol). И сегодня реализован практически на всём сетевом оборудовании.  Практически ;)

Сам протокол не суперпростой, но базовые принципы его работы незамысловаты.

Во‑первых, в качестве метода аутентификации на сетевом оборудовании используется пароль.

На самом деле в самом протоколе куча вариантов

TAC_PLUS_AUTHEN_TYPE_PAP := 0x02
TAC_PLUS_AUTHEN_TYPE_CHAP := 0x03
TAC_PLUS_AUTHEN_TYPE_ARAP := 0x04 (deprecated)
TAC_PLUS_AUTHEN_TYPE_MSCHAP := 0x05
TAC_PLUS_AUTHEN_TYPE_MSCHAPV2 := 0x06

Мы из этого добра выбираем самый простой (ASCII). Просто потому, что для наших одноразовых паролей это самое понятное и рабочее.

Во‑вторых, протокол работает поверх TCP 49.

В‑третьих, он обеспечивает AAA: Authentication, Authorization, Accounting.

В‑четвёртых, по умолчанию каждый вид операции осуществляется в отдельной сессии.

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

И в‑шестых, каждое сообщение TACACS+ шифруется с помощью этого секрета — не только пароль, но и всё содержимое.

Как вы можете видеть, TACACS не работает с ключами — только с паролями. И снова добрый день. А ещё тут и шаренные секреты. В итоге стандартный механизм работы выглядит так:

TACACS, такой..
  1. Пользователь с паролем приходит по SSH на оборудование.

  2. Оборудование устанавливает сессию с TACACS‑сервером.

  3. Оборудование шифрует запрос TACACS с помощью общего секрета, указанного в конфигурации.

  4. В этой сессии спрашивает: «Вот такой‑то  Семён Иванович с таким‑то паролем  semyon1984 может ли зайти на железку?»

  5. Сервер аутентифицирует пользователя. И при успехе возвращает положительный ответ.

  6. Клиент обычно сразу же отправляет зашифрованный запрос на авторизацию.

  7. Сервер возвращает уровень доступа пользователя (privilege level например).

  8. Оборудование разрешает подключение пользователя.

  9. Далее в зависимости от конфигурации пользователь либо уже работает, либо на выполнение каждой команды будет ещё запрос аккаунтинга на TACACS‑сервер. Если других механизмов (например, Bastion) нет, то это вполне себе полезная функция.

Стоит заметить, что, строго говоря, согласно RFC, аутентификация, авторизация и аккаунтинг не являются обязательными.

Примеры конфигураций

Juniper:

set system tacplus-server 2a0d:d6c0:1ce:1ce::babe routing-instance mgmt_junos
set system tacplus-server 2a0d:d6c0:1ce:1ce::babe secret "SOMESECRET"
set system tacplus-server 2a0d:d6c0:1ce:1ce::babe port 49
set system authentication-order [ password tacplus ]

class mon {
    permissions [ view view-configuration ];
    allow-commands "SOMEVERYUSEFULUNDIMPORTANTCOMMANDS";
}

class super-user{
    idle-timeout 15;
    permissions all;
}

user RO {
    uid 4996;
    class mon;
}

user SU {
    uid 4997;
}

set system login user RO class mon
set system login user SU class super-user

Huawei:

hwtacacs enable
hwtacacs server template tacacs
hwtacacs server authentication 2a0d:d6c0:1ce:1ce::babe 49 VPN-instance MGMT
hwtacacs server authorization 2a0d:d6c0:1ce:1ce::babe 49 VPN-instance MGMT
hwtacacs server accounting 2a0d:d6c0:1ce:1ce::babe 49 VPN-instance MGMT
hwtacacs server shared-key cipher "SOMESECRET"

aaa
 authentication-scheme tacacs
  authentication-mode local hwtacacs
 authorization-scheme tacacs
  authorization-mode local hwtacacs
 accounting-scheme tacacs
  accounting-mode hwtacacs

domain default_admin
  authentication-scheme tacacs
  accounting-scheme tacacs

domain localuser
  authentication-scheme tacacs
  authorization-scheme tacacs
  accounting-scheme tacacs
  hwtacacs server tacacs

  default-domain admin localuser

Cisco:

aaa new-model

tacacs server tacacs-bastion
  address ipv6 2a0d:d6c0:1ce:1ce::babe
  key 7 <tacacs secret key>

aaa group server tacacs+ tacacs-bastion-group
  server name tacacs-bastion

aaa authentication login default local group tacacs+
aaa authorization exec default local group tacacs+

line vty 0 4
  login authentication default

line vty 5 15
  login authentication default

Всё неплохо? Или нет? Где в этой схеме узкие места?

  1. Конечно, пароль. Раз есть пароль, значит, надо его ротировать. Не суперсложно: менять‑то всего лишь на TACACS‑сервере и в голове инженера, но всё равно это надо делать.

  2. Пользователям надо вводить пароль! Да они вас со свету сживут после удобства, которое дают SSH‑ключи.

  3. У нас всё ещё на железке есть пароль — теперь это секрет TACACS. Его тоже нужно ротировать. А как это делать, не теряя доступа к железу?

  4. Отказоустойчивость TACACS‑сервиса? На железе обычно нельзя настроить несколько адресов. Пачка воркеров с балансировкой? А так точно сработает, учитывая разные сессии на аутентификацию и авторизацию? А что с паролем? Как его консистентно хранить между Bastion и TACACS‑серверами?

  5. А что, если TACACS‑сервис всё‑таки сломается? Ну совсем прям — что делаем?

Мы решили все эти проблемы. Рассказываем, как.

Ввод пароля и аккаунтинг

Мы не придумали ничего лучше, чем… сохранить в цепочке подключения бастионный хост и заставить его притворяться пользователем.

Осчастливливаем клиента с помощью TACACS, Bastion и
  1. То есть я, пытаясь подключиться на сетевую железку, на самом деле по‑прежнему иду на Bastion, где авторизуюсь через сертификат — то есть никакого ввода пароля и его ротации. Уже ура!

  2. Бастионный хост, видя, что я иду на сетевое железо, берёт моё имя пользователя, генерирует временный пароль (OTP — One‑Time Password). И уже с этим кредами пытается подключиться на железку.

  3. На железке у нас настроена работа TACACS, поэтому этого пользователя она пытается проверить на TACACS‑сервере.

  4. TACACS‑сервер в нашем случае — это отдельные от Bastion сущности, на которых реализован тот же механизм работы с временными ключами, что и на самом Bastion. Поэтому в течение короткого периода времени этот пароль действует, и TACACS возвращает успех на запрос аутентификации.

То есть ещё раз: клиент аутентифицируется на Bastion через сертификат без ввода пароля. Bastion аутентифицируется на сетевой коробке через сгенерированные временные логин и пароль, используя TACACS‑сервер.

Тем самым мы сразу закрыли первые два пункта с узкими местами: необходимость пользователю вводить пароль и его периодическая ротация.

OTP

Тут стоит дать пояснение, что за временный пароль и как этот пароль, сгенерированный Bastion, узнаёт TACACS‑сервер.

Самый простой вариант — писать этот пароль в базу на Bastion, читать его на TACACS‑сервере. И всё. Мы сначала так и делали, но это неизящно и, честно говоря, некрасиво. Да и к тому же неэлегантно.

Поэтому мы обратились к TOTP — Time‑based One Time Password (даже есть RFC). Америку мы тут не открыли, механизмы давно известны и написаны. На Bastion генерируется TOTP на основе какого‑то общего секрета и движущегося промежутка времени. TACACS тоже знает этот общий секрет. И пока запрос попадает в этот короткий промежуток времени, пароль валиден.

Такой принцип позволяет держать пул бастионов и пул TACACS‑серверов совершенно независимыми. И более того: сколько бы ни было экземпляров TACACS‑сервера, запрос на любой из них приведёт к одинаковому решению. Прекрасно! Главное — следить за тем, чтобы время сильно не разбегалось между серверами Bastion и TACACS, не забывать об NTP и вот это вот всё.

Далее идём к следующему пункту.

Отказоустойчивость

Bastion отказоустойчив изначально. Это стейтлесс‑сервис*, который находится за балансировщиком нагрузки и распределён по нескольким бэкендам.

Поход от ssh-клиента в Bastion через балансировщик нагрузки
Ладно, будем честны сами с собой

Стейтлесс он за счёт того, что используемый стейт берёт из внешних сервисов. В частности, Access Service в IAM (какой такой IAM — читайте дальше), так как именно на основании ответов IAM он вообще назначает пользователю ту или иную роль или уровень привилегий в терминах TACACS. Но при этом Access Service исходно обложен кешами снизу доверху, рассчитан на большую readonly‑нагрузку, отдельная команда следит за его состоянием, и он там по‑своему отказоустойчив.

А Bastion — стейтлесс за балансером, да)

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

Поход от сетевого устройства в TACACS через балансировщик нагрузки

Тут тоже ничего замысловатого.

Общая схема будет выглядеть так:

Балансировщики! Кругом балансировщики!

Про то, как работают балансеры, можно послушать в докладе моего коллеги или почитать в статье разработчика балансировщиков.

Ротация секрета TACACS

В чём вообще проблема? В том, что если мы поменяем секрет на сервере, то сразу после этого доступ на железо отвалится до тех пор, пока мы не поменяем секреты и на нём. То есть можем оказаться в ситуации, когда часть оборудования доступна, а часть — нет.

И это решается довольно просто: мы добавили в TACACS‑сервер поддержку работы одновременно нескольких секретов. То есть сервер пытается расшифровать приходящий запрос по очереди разными секретами.

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

Ну и последнее: что делать, когда всё совсем плохо?

Breakglass

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

Примеры конфигураций

Huawei:

authentication-scheme tacacs
  authentication-mode local hwtacacs
 authorization-scheme tacacs
  authorization-mode local hwtacacs

Juniper:

authentication-order [ password tacplus ];

Cisco:

aaa authentication login default local group tacacs+
aaa authorization exec default local group tacacs+

Так мы можем обезопасить себя от того, что сервис TACACS безвозвратно сломан или возвращает мусор.

И локальные пользователи у нас всё‑таки есть. Самый главный из них — это breakglass. Это пользователь с паролем, хранящимся в надёжном месте, к которому мы прибегаем, когда всё плохо (ещё ни разу не приходилось).

  • Если лежит TACACS‑сервер, мы можем подключиться под breakglass потому что локальные пользователи проверяются раньше.

  • Если лежит сеть управления, устройство недоступно по IP, мы можем подключиться через консольный порт под breakglass.

Кроме breakglass у нас есть ещё два локальных пользователя — это роботы. Под одним на оборудование ходит система мониторинга для сбора метрик — у него права только read‑only. Под другим ходят системы автоматизации — у него read‑n-write.

Мы сделали это намеренно, чтобы не усложнять работу систем использованием Bastion. Автоматизированные системы работают из доверенного контура внутри облака, операции логируются, релизы проходят двойное подтверждение, а если на ресурсы заходить по SSH, то это опять же через Bastion. Со всех сторон прикрылись.

Почти.

А как насчёт честного Breakglass?

Что, если развалится не только сервис TACACS, но и Bastion? Что, если развалится инфраструктура, на которой он поднят? Тем более что бастионные хосты расположены в сети, которой мы и управляем.

Что, если сломается тот контур, из которого подключаются внутренние системы?

Как же мы тогда подключимся к железу?

А на этот случай у нас есть прямой доступ на железо и по 22 порту в обход Bastion и, как я уже упоминал выше, консольному порту. У всего отдела.

Стоп, что?!

Мониторинги

А как же правило, что весь SSH на инфраструктуре только через Bastion? Всё должно аудироваться? Можно прервать подозрительную сессию? Мы только что рассказывали про реализовавшуюся в жизни мечту любого безопасника! А теперь — прямой доступ и локальные пользователи!

Тут у нас с одной стороны джентльменское соглашение, что инженеры не ходят напрямую, а с другой — мониторинг. Мы просто отстояли своё законное право  дропать пакеты иметь подключение на случай глобальных инцидентов.

Первое средство мониторинга — это контроль подключений к оборудованию. Как я писал выше, если система мониторинга обнаруживает доступ в обход Bastion (то есть src IP не принадлежит Bastion) или использование УЗ breakglass, она поднимает аварию и призывает всех, до кого дотянется.

Чтобы это стало возможно, каждая железка шлёт syslog в специальный коллектор, над которым работает несложная аналитическая система, выцепляющая логи подключения по SSH.

Примеры логов доступа с железок разных вендоров:

Huawei:

Apr 27 2024 06:45:07.021 some-weihua1 %01SSH/5/SSH_USER_LOGIN(s):CID=0x80932723;The SSH user succeeded in logging in. (ServiceType=stelnet, UserName=setevik, UserAddress=SRC_IP, LocalAddress=DST_IP, VPNInstanceName=MGMT)

Juniper:

May  3 01:40:29  some_perjuni sshd[65425]: Accepted publickey for setevik from SRC_IP port 37794 ssh2: RSA SHA256:pqFIC+PU7Qs6xxtrGnzxFrh3yKWNndIqUMBZZVE8HsM

Cisco:

*May  3 01:22:27.236: %DMI-5-AUTH_PASSED: R0/0: dmiauthd: User 'setevik' authenticated successfully from SRC_IP:48878 and was authorized for netconf over ssh. External groups: PRIV15

А чтобы быть уверенным, что никто не забыт, мониторится также и то, что логи приходят со всех сетевых коробок. Для этого они выгружаются из инвентарной системы, а затем проверяется тот факт, что с каждой доступной коробки логи приходят, иначе… Ну вы поняли.

Второе — это мониторинг работоспособности пользователя breakglass. В механизме breakglass нет никакой ценности, если мы не убеждены, что он работает. Поэтому у нас в кронах крутится простенький скриптик, который раз в день подключается на весь парк железа под этим пользователем и с его паролем. Если куда‑то не получилось — страшно верещит, предупреждая об опасности. Такое случается «планово»: например, когда мы меняем пароль в общей секретнице, а на оборудовании ещё не успели поротировать.

Для демонстрации поменял пароль на одной железке — сразу (как крон сработал) увидели на дашике
И в телегу прилетело
А потом мы чиним — и приятно

Ещё один мониторинг — это end‑to‑end‑тесты работоспособности сервисов для проверки релизов Bastion и TACACS‑сервера. Надо бы, чтобы они не ломали доступ на сетевое оборудование, про которое можно и случайно позабыть в угаре написания кода.

Для этого мы в виртуалках в облаке развернули пару CSR1000v. Сначала релизы Bastion выкатываются на препрод, и мониторинг проверяет, что там всё OK (на препроде используется отличный от продового секрет TACACS). Потом точно так же, чтобы не беспокоить продовые коробки под парами, он ходит на вторую циску с продовыми кредами и проверяет её.

Ну и плюс постоянный E2E‑мониторинг во время нормальной работы.

Немного сахара

В целом, использовать ли TACACS, Bastion определяет по FQDN, а точнее — по доменной части. Грубо говоря, если домен seteviki.nuzhny.net, то Bastion точно использует TACACS, а если нет — точно нет.

Однако исторически в этом же домене у нашей команды находятся и физические Linux‑хосты, и виртуалки с сервисами, поэтому на одни адреса нужно использовать TACACS, а на другие — сертификаты.

Мы научили Bastion на лету определять, использовать TACACS или сертификаты. Ну как на лету… Примерно как лицо в состоянии алкогольного опьянения стоит у двери и перебирает ключи в связке в надежде, что хоть один подойдёт — так и Bastion пробует авторизоваться через TACACS, и если не получается, то сертификаты.

Почему такой порядок? Потому что сетевые железки обычно чувствительны к количеству неудачных аутентификаций и могут легко заблокировать пользователя.

А чтобы не вводить длинный FQDN, в клиенте‑обёртке, который позволяет работать через Bastion, сделали автопоиск домена среди списка разрешённых — аналог записи domain в resolv.conf.

Таким образом, мы вводим короткое pssh some-switch1 вместо pssh some-switch1.seteviki.nuzhny.net.

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

А для пользователя это проходит максимально незаметно. Ну, если не «увы и ах», конечно.

rw_, ro_

Часть сетевых устройств грешит тем, что если пользователь уже есть в локальной базе, то они отбивают подключение, даже не обращаясь к TACACS. Что ж, имеют право — клауд‑титан им судья.

Поэтому мы, помучившись совсем недолго, стали добавлять к имени пользователя префикс rw_ или ro_ в зависимости от того, какими правами он обладает. Не слишком большая беда, но на время миграции с локальной базы на TACACS беспокоило будь здоров!

Но история за rw_ или ro_ глубже. Дело в запросе на авторизацию, происходящем как бы в вакууме. Если аутентификация проходит через Bastion, который из метаданных сертификата знает даже о цвете нижнего белья пользователя, то авторизация — это короткое сообщение между сетевой коробкой и TACACS‑сервером, содержащее только имя пользователя.

Поэтому такой префикс в нашем случае — прямое указание TACACS, какие права выдать пользователю.

Вообще, конечно, у разных вендоров разные уровни доступа, роли могут быть очень даже развесистые и гранулярные. При этом мы берём и мощно сплющиваем всю эту сложность в два уровня всего: чтец и  жнец админ —  нам достаточно.

SCP и NETCONF

Как оказалось, SCP описан неидеально. И реализовать его можно по‑разному: так, например, что с Linux‑хостами он работать будет, а с сетевым оборудованием — нет.

В итоге в нашем самодельном клиенте SCP был реализован очень интересно. И первая встреча с кастомной вендорской версией показала комплементарность этой интересности. Пришлось переписать SCP‑клиент, и теперь он работает со свитчами через Bastion.

А ещё поддержали также работу через него NETCONF — интерфейс, который критичен для нас в ряде сценариев.

Юбик поглаживать

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

Нужно, чтобы парой «сертификат + приватный ключ» не могли воспользоваться посторонние. Ну и в конце концов, если вы допустите появление злоумышленника на вашей машине, важно, чтобы он не смог воспользоваться SSH. И тут есть две разные школы.

Одна — это MFA (Multi‑Factor Authentication). По сути, использование SSO (Single Sign‑On) для SSH. При попытке подключения открывается сайт внутреннего портала, заставляющий вас пройти дополнительную аутентификацию через  SMS (Short Message Service) или программный ключ.

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

pssh some-switch1
Issuing new session certificate
Please touch yubikey OK
rw_eucariot@some-switch1>

У каждого из способов есть свои плюсы и минусы. Но оба делают процесс подключения значительно безопаснее, пусть и немного усложняя его.

Интеграция с ABC

Естественно, мы не управляем списком пользователей на бастионе и TACACS вручную. Ни для сетевых коробок, ни для серверного парка. Благо в компании с без малого тридцатилетней историей есть внутренняя зрелая система контроля доступов, а в облаке есть IAM — Identity and Access Management.

Рассказываем.

У нас в компании есть сервисы. Боже, много сервисов! Каждый из них олицетворяет собой некоторую сущность с понятными формами и содержанием. Как примеры: S3, YC Production, YC Testing, Network API, YC VPC, NOC . Они сгруппированы по тому, какую услугу предоставляют. Но при этом могут представлять собой как фактический программный сервис в Яндексе (например, Network API ), так и команду людей (например, NOC — Network Operation Center).

Такая система управления сервисами называется ABC. Сервисы позволяют добавлять в них людей с разными ролями. Так, например, у кого‑то в YC Production может быть роль администратора с широчайшими правами, у другого — DevOps только для задач деплоя и обслуживания, у кого‑то — оператор для просмотра и выполнения базовых операций.

В Yandex Cloud же есть IAM — публично доступный сервис Identity and Access Management. Он реализует функции аутентификации и авторизации. Естественно, облачный сервис Bastion оперирует объектами IAM — субъектами, ресурсами, ролями, разрешениями.

Мы интегрировали эти две системы друг с другом, завели в ABC сервис YC TACACS, добавили в него всех сетевых инженеров как администраторов и настроили IAM так, что людям, имеющим такую роль, TACACS выдаёт privilege level 15. А ещё добавили роль чтец для RO: им TACACS выдаёт privilege level 3 (в терминах Cisco).

Это мог бы быть твой сервис, но ты не в Яндексе

То есть права в ABC прорастают в облачный IAM, а сервис Bastion на каждый запрос аутентификации обращается в IAM, чтобы понять, можно ли в принципе этому пользователю заходить на устройство и с каким уровнем привилегий.

Теперь управление правами на железе при найме и увольнении сводится к изменению состава этого сервиса. А все изменения прорастают автоматически. Весь вопрос — 30 минут.

Если вы не Яндекс, то решения в опенсорсе позволяют добавить интеграцию с Active Directory, но мы этого не проверяли.

Весь процесс

Давайте теперь проследим весь путь от нажатия Enter на ноутбуке инженера до строки приглашения.

  1. Инженер вводит pssh some-switch1 или ssh some-switch1 с настроенным ssh‑agent.

  2. Используя приватный ключ и SSH‑сертификат, программа начинает устанавливать соединение с Bastion.

  3. Соединение приходит на балансировщик нагрузки, который уже передаёт его на какой‑то конкретный бастионный хост.

  4. Bastion сначала аутентифицирует пользователя по сертификату, затем обращается в IAM и проверяет, есть ли у этого пользователя нужная роль для использования Bastion: если да, разрешает установление сессии с собой.

  5. По доменному имени или IP‑адресу целевого узла Bastion понимает, что это сетевое оборудование. Также Bastion в IAM узнаёт роль пользователя на сетевом оборудовании.

  6. Bastion генерирует временный пароль TOTP и устанавливает SSH до сетевого устройства с именем пользователя rw/ro_<username> и этим временным паролем.

  7. Сетевое устройство проверяет локальную базу пользователей. Если не находит такого, то обращается к TACACS‑серверу с запросом аутентификации, шифруя сообщение общим секретом.

  8. Соединение к TACACS‑серверу проходит через балансировщик нагрузки, который распределяет его уже на конкретный TACACS‑хост.

  9. TACACS‑сервер расшифровывает запрос, видит пароль, сверяет его с тем, что вычисляет сам. При совпадении — разрешает.

  10. TACACS‑сервер отвечает, сетевое оборудование разрешает или запрещает подключение.

  11. Оборудование делает повторный запрос — уже на авторизацию. Он аналогично проходит через балансировщик нагрузки.

  12. TACACS‑сервер расшифровывает запрос, видит префикс пользователя  ro/rw и именно так определяет, какой уровень доступа вернуть.

  13. TACACS возвращает ответ — соединение установлено.

Разбор в wireshark

Напоследок обезжиренное (по сравнению с моими предыдущими статьями) мясцо — если хочется закопаться в дампы.

На скрине мы видим установку TCP‑сессии по 49 порту.

Далее в четвёртом сообщении зашифрованный запрос на аутентификацию. Непродолжительный обмен данными и закрытие сессии в десятом сообщении.

Ну, всё правильно — Single Connection: Not Set. Но по RFC можно.

А уже в тринадцатом сообщении — повторная установка сессии, после которой следует авторизация и снова закрытие сессии. Расточительно? Возможно. Замечаем ли мы это? Нет.

Скучновато с зашифрованным телом сообщений TACACS? Да!

Давайте расшифруем!

Шаг 1. Заходим в настройки, выбираем пункт Protocols.

Шаг 2. Выбираем TACACS+ и вводим свой секрет TACACS.

Шаг 3. Наслаждаемся Decrypted Request/Reply.

Запрос аутентификации (пакет 4):

Ответ сервера (пакет 6):

Клиент шлёт пароль (пакет 7):

Аутентификация пройдена (пакет 9):

Запрос авторизации (пакет 17):

Тут видно, что это Juniper запрашивает доступный уровень исполнения.

И ответ сервера (пакет 19). Мол, используй, дружочек, права локального пользователя  SU Ну ок.

В целом протокол довольно незамысловатый, хотя и нетривиальный.

Вместо заключения

В итоге получилось централизованное управление доступами на железо с простой ротацией секретов, механизмом breakglass и мониторингом всего и вся. Вся связка развёрнута прямо на инфраструктуре Yandex Cloud с использованием инстанс‑групп, балансировщиков нагрузки, IAM, MDB, что позволяет управлять всей инфраструктурой стандартными средствами: terraform, утилита yc, консоль Yandex Cloud. Решение отказоустойчивое на всех сегментах.

А проблему кольцевых зависимостей, то есть ситуации, когда лёг сервис Bastion, балансировщик нагрузки, оверлейная сеть, мы решаем за счёт механизма breakglass.

Что клёво, как мне кажется, — не пришлось изобретать рядом едущий велосипед. Мы встроили сервис TACACS в существующую инфраструктуру Bastion.

Ну и, отвечая на немой вопрос: нет, в опенсорс обратно не выкладывали и пока про это не думали.


Полезные материалы


Спасибы

  • Олегу Андрееву, провернувшему эту гору работы, рассказавшему об этом на nexthop и подкасте и рецензировавшему эту статью.

  • Владу Старостину, который изначально в Яндекс втащил TACACS.

  • Моим коллегам за первичную имплементацию, интеграции, деплой cisco csr1000v aka tacacstion, рон, мониторящий работоспособность механизма breakglass, тшут и многое‑многое другое вокруг Bastion и TACACS


N.B. Вот есть у вас сотня коммутаторов или маршрутизаторов. И это не Mikrotik…

*В иллюстрациях использованы образы персонажей Gravity Falls series by Alex Hirsch, Disney Television Animation