Bastion-хост, статичные SSH-ключи, отдельный kubeconfig для каждого разработчика и ещё один пароль для psql — типичный набор доступа к продакшн-инфраструктуре, в котором никто не может быстро ответить на вопрос «кто именно и что делал на этом сервере во вторник в 3 часа ночи». Zero-trust VPN вроде NetBird или Cloudflare Tunnel эту проблему не решает — он даёт зашифрованный сетевой путь до периметра, но что происходит после подключения, остаётся на совести SSH-ключей и логов auth.log на самой машине. Teleport решает другую задачу: не «как попасть в сеть», а «как получить доступ к конкретному ресурсу — поду, базе, серверу — так, чтобы каждое действие было аудируемо и отзываемо».
Содержание
Открыть содержание
VPN и Teleport закрывают разные слои
NetBird и Cloudflare Tunnel — это сетевой уровень: они решают, кто может достучаться до приватной подсети или конкретного хоста по IP и порту. Внутри этой подсети всё по-старому: SSH по ключу, kubectl с локальным kubeconfig, psql с паролем из .pgpass. Zero-trust на уровне сети — необходимое, но не достаточное условие для privileged-доступа: SOC2 и ISO 27001 спрашивают не «кто мог физически достучаться до базы», а «кто именно и когда выполнял конкретные команды в production», и сетевой VPN на этот вопрос ответить не может в принципе — он не видит, что происходит выше транспортного уровня.
Teleport встаёт не вместо VPN, а поверх произвольной сети (в том числе поверх уже настроенного NetBird или Cloudflare Tunnel) и выдаёт доступ не к сети, а к ресурсу: конкретному серверу по имени, конкретному Kubernetes-кластеру, конкретной базе данных. Разница на практике: сетевой VPN не может дать «доступ только на чтение к поду checkout в namespace prod на 20 минут» — а Teleport может, потому что RBAC у него привязан к ресурсу и роли, а не к CIDR.
Проблема статичных SSH-ключей и kubeconfig
Классическая связка bastion + SSH-ключи ломается на масштабе предсказуемо:
- Ключ не имеет TTL. Скомпрометированный
id_rsaработает, пока его вручную не отозвали из всехauthorized_keysна всех хостах — а это ручная операция, которую в спешке инцидента забывают сделать на паре машин. - kubeconfig — это плоский bearer-токен или client-cert без встроенного аудита. Кто угодно с файлом на диске может делать
kubectl execв любой под, на который у токена есть права, и в логах кластера останется только имя ServiceAccount или CN сертификата — не имя человека. - Доступ к базе — отдельная история. Пароль к продакшн Postgres обычно живёт в password-менеджере, secret-манагере или прямо в
.pgpass, и его ротация — отдельный забытый пункт в чек-листе оффбординга.
Всё это — статичные секреты с неограниченным сроком жизни, которые утекают одинаково: через скомпрометированный ноутбук, забытый доступ у уволенного сотрудника или CI-раннер, чей workspace попал не в те руки. Teleport убирает статичные секреты как класс, заменяя их короткоживущими сертификатами.
Как работают короткоживущие сертификаты
Teleport Auth Service — это внутренний CA. Пользователь логинится один раз через tsh login (по SSO — Okta, Google Workspace, GitHub — или через существующий SPIFFE/SPIRE как источник identity), и в ответ получает не пароль, а X.509- и SSH-сертификат с TTL по умолчанию 8–12 часов. Дальше:
- SSH — Teleport подписывает сертификат хоста и сертификат пользователя;
sshдоверяет CA Teleport, а не набору отдельных публичных ключей на каждой машине. - Kubernetes — вместо статичного kubeconfig
tsh kube login <cluster>генерирует kubeconfig с клиентским сертификатом, привязанным к личности пользователя и валидным ровно до истечения сессии. - Базы данных — Teleport Database Service проксирует соединение к Postgres/MySQL/MongoDB, подменяя пароль на mTLS-сертификат; приложению или человеку никогда не нужно знать реальный пароль базы.
Когда сертификат истекает, доступ пропадает сам — без ручного отзыва ключей на десятках хостов. Утечка сертификата ограничена по времени и по объёму: он привязан к ролям пользователя на момент выдачи, а не к статичному ключу, который работает, пока кто-то не вспомнит его удалить.
RBAC на уровне ресурса, а не сети
Роли в Teleport (teleport.yaml → roles) описывают доступ в терминах меток на ресурсах: env: prod, app: checkout, конкретное имя базы данных или namespace. Это принципиально другой уровень гранулярности по сравнению с сетевым ACL:
- сетевой ACL может разрешить или запретить доступ к порту 6443 (Kubernetes API) целиком;
- роль Teleport может разрешить
kubectl get podsв namespacestaging, но запретитьexecи полностью закрыть namespaceprod— при этом оба доступа идут через один и тот же сетевой путь.
Комбинация с существующим identity-провайдером — SSO или SPIFFE/SPIRE, если в кластере уже развёрнут SPIRE как источник workload identity — означает, что роли Teleport мапятся на группы SSO или на SPIFFE ID, а не заводятся вручную для каждого нового человека. Доступ выдаётся и отзывается там же, где заводится и увольняется сотрудник — в HR-системе или каталоге identity, а не в отдельном списке authorized_keys.
Session recording и разбор инцидента
Каждая SSH-сессия, kubectl exec или запрос к базе через Teleport проксируется через Auth/Proxy Service, и по умолчанию записывается как session recording — терминальный ввод-вывод с таймкодами. После инцидента разбор не требует восстановления состояния хоста из истории bash (которую пользователь мог подчистить) — достаточно:
tsh recordings ls --format=json | jq '.[] | select(.user=="alice")'
tsh play <session-id>
tsh play воспроизводит сессию как терминальную запись — посимвольно, в реальном темпе или ускоренно. Для аудита SOC2/ISO это не «мы верим, что доступ был ограничен» — это конкретная запись, кто, когда и что именно вводил в shell на проде.
Что нужно, чтобы поднять доступ к тестовому кластеру Kubernetes
Минимальный стенд: Teleport cluster (self-hosted teleport или managed Teleport Cloud) + tbot для получения identity в CI/автоматизации + один Kubernetes-кластер, зарегистрированный как ресурс.
Устанавливаем teleport и логинимся:
# на машине с Teleport Auth/Proxy Service
curl https://cdn.teleport.dev/install.sh | bash -s 17.0.0
# с рабочей станции
tsh login --proxy=teleport.example.com --user=alice
Регистрируем Kubernetes-кластер как ресурс, доступный через Teleport (агент teleport-kube-agent разворачивается прямо в кластере как Helm chart):
helm repo add teleport https://charts.releases.teleport.dev
helm install teleport-kube-agent teleport/teleport-kube-agent \
--set roles=kube \
--set proxyAddr=teleport.example.com:443 \
--set kubeClusterName=prod-cluster \
--create-namespace -n teleport
Роль, ограничивающая доступ до чтения в конкретном namespace (файл reader.yaml, применяется через tctl create -f):
kind: role
version: v7
metadata:
name: kube-reader-checkout
spec:
allow:
kubernetes_labels:
env: staging
kubernetes_resources:
- kind: pod
namespace: checkout
verbs: ["get", "list"]
Получаем доступ вместо статичного kubeconfig:
tsh kube login prod-cluster
kubectl get pods -n checkout
Никакого файла kubeconfig с бессрочным токеном на диске не остаётся — tsh kube login встраивает в локальный kubeconfig сертификат, привязанный к текущей сессии tsh, и он перестаёт работать вместе с истечением TTL логина.
Как проверить, что это работает
Смотрим, что kubectl exec в этом кластере попал в запись сессии, а не только в audit log Kubernetes API:
kubectl exec -it -n checkout deploy/checkout -- sh
# в другом терминале, с ролью аудитора
tsh recordings ls --format=json | jq '.[-1]'
tsh play $(tsh recordings ls --format=json | jq -r '.[-1].id')
Если сессия проигрывается посимвольно — proxy действительно терминирует и пишет каждое подключение, а не просто маршрутизирует трафик до API-сервера напрямую.
Итог
VPN (NetBird, Cloudflare Tunnel) отвечает на вопрос «кто может достучаться до сети» — это правильный и достаточный слой для доступа к внутренним сервисам, которые сами по себе не privileged. Teleport отвечает на другой вопрос — «кто, к какому именно ресурсу и с каким аудиторским следом» — и это тот слой, который обязателен, когда речь идёт о продакшн-базе, проде Kubernetes или любом доступе, который должен пройти SOC2/ISO-аудит. Разворачивать оба сразу не обязательно: если сетевой VPN уже есть и privileged-доступа как отдельной задачи ещё не стоит, можно подождать. Но в момент, когда впервые звучит вопрос «а кто заходил в prod вчера ночью», ответом должен быть tsh play, а не грепанье .bash_history.