Windows Server для самых маленьких. Часть 30: Как работает единый вход через Kerberos?
Все части серии «Windows Server для самых маленьких»В части 29 alice вошла на lnx1 с паролем, и после входа у неё появился TGT. В части 6 было описано, что с TGT можно получить билет на любую службу, не предъявляя пароль ещё раз. На Windows это происходит само: \\srv1\share, RDP, WinRM, LDAP — пароль не спрашивают никогда. На Linux и между платформами то же самое возможно, но не включено по умолчанию и требует понимания двух вещей: как приложение добирается до Kerberos и как служба получает свой ключ. Эта часть — о едином входе с Linux-стороны: SSH, веб, базы данных и всё, что говорит GSSAPI.
GSSAPI и Negotiate
Приложения не работают с Kerberos напрямую. Между ними — GSSAPI (Generic Security Services API, RFC 2743): стандартный интерфейс, за которым может стоять Kerberos (механизм описан в RFC 4121), NTLM или что-то ещё. Приложение просит «установить контекст безопасности с host/lnx1.corp.example.com», библиотека сама получает билет, собирает AP-REQ и возвращает токен, который приложение отправляет серверу по своему протоколу; сервер отдаёт токен своей библиотеке, та проверяет его ключом из keytab и возвращает имя клиента. Приложению всё равно, что внутри токена: оно только переносит байты туда и обратно.
Поверх GSSAPI — SPNEGO (RFC 4178), он же Negotiate: согласование механизма, когда стороны могут знать разные. Первый токен клиента перечисляет механизмы, которые он умеет, и сразу несёт токен предпочтительного (обычно Kerberos), сервер отвечает, какой выбран. Windows использует SPNEGO везде, Linux — там, где нужно совместимо с Windows. Где это встроено:
| Протокол | Механизм | Как выглядит |
|---|---|---|
| SSH | gssapi-with-mic (RFC 4462) |
GSSAPIAuthentication yes с обеих сторон |
| HTTP | Negotiate (RFC 4559) |
заголовок WWW-Authenticate: Negotiate, токен в Authorization |
| LDAP | SASL GSSAPI или GSS-SPNEGO |
часть 5 |
| SMB | SPNEGO в Session Setup | Samba с security = ads, smbclient --use-kerberos=required |
| PostgreSQL | gss в pg_hba.conf |
SPN postgres/host |
| NFSv4 | sec=krb5, krb5i, krb5p |
SPN nfs/host, rpc.gssd |
| RPC и DCOM на Windows | Negotiate | всё из части 15 |
SASL в строке LDAP — рамка подключаемых механизмов аутентификации внутри протокола: клиент называет механизм (GSSAPI — Kerberos через GSSAPI, GSS-SPNEGO — то же через SPNEGO) и обменивается его токенами в операции Bind. SMB — протокол файловых ресурсов Windows, его реализация на Linux — Samba. PostgreSQL — сервер баз данных со своим протоколом поверх TCP 5432. NFS — сетевая файловая система Unix, в версии 4 с аутентификацией через RPCSEC_GSS; об этих трёх подробнее ниже. RPC и DCOM — удалённый вызов процедур Windows, на котором держатся оснастки и WMI.
Библиотека на Linux — MIT Kerberos (libkrb5, libgssapi_krb5), на Windows — SSPI, и они совместимы на уровне протокола, потому что Kerberos один.
SSH
Linux → Linux. У lnx1 в keytab есть host/lnx1.corp.example.com с момента вступления; sshd нужно разрешить механизм:
$ sudo tee /etc/ssh/sshd_config.d/60-gssapi.conf <<'EOF'
GSSAPIAuthentication yes
GSSAPICleanupCredentials yes
EOF
$ sudo systemctl reload ssh
GSSAPICleanupCredentials yes (это и умолчание) удаляет пересланный TGT пользователя, когда сеанс закрывается. Ещё одно умолчание, GSSAPIStrictAcceptorCheck yes, требует, чтобы билет был выписан именно на host/<имя этого сервера>: билет на другое имя из того же keytab sshd не примет.
На клиенте, где у alice есть TGT (вход с паролем через sssd или kinit alice), вход выглядит как обычный ssh:
alice@lnx2:~$ ssh -K lnx1.corp.example.com hostname
-K включает GSSAPI и делегирование на один вызов; постоянно — в ~/.ssh/config или /etc/ssh/ssh_config.d/:
Host *.corp.example.com
GSSAPIAuthentication yes
GSSAPIDelegateCredentials yes
Клиент OpenSSH пробует GSSAPI первым (PreferredAuthentications по умолчанию начинается с gssapi-with-mic), а ключ и пароль остаются запасными методами там, где билета нет. Как выглядит обмен в ssh -v, показано в «Смотрим руками».
Делегирование здесь — то же, что в части 16: вместе с билетом на сервер клиент пересылает TGT, он уезжает на lnx1, и там klist -f покажет у TGT флаг f (forwarded), после чего с lnx1 можно идти дальше — на \\srv1, на lnx3, в LDAP — от имени alice. И тот же риск: TGT пользователя на сервере, и любой, кто там root, может им воспользоваться. Клиент MIT Kerberos пересылает TGT, когда его просят, независимо от того, что думает о сервере каталог (enforce_ok_as_delegate = false по умолчанию в krb5.conf). Клиент Windows — нет: по спецификации MS-KILE он пересылает TGT только серверу, чей билет несёт флаг ok_as_delegate, а KDC ставит его учётным записям с доверием для неограниченного делегирования (часть 6). Контроллеры домена такое доверие имеют, LNX1$ — нет.
Windows → Linux. Встроенный клиент Windows из части 16 с GSSAPIAuthentication yes в ~\.ssh\config входит на lnx1 билетом из сеанса Windows: SPN host/lnx1.corp.example.com у LNX1$ есть, keytab совпадает, и Windows-клиенту всё равно, что за sshd на той стороне. Имя пользователя — -l alice или User alice в той же секции: без него клиент подставит corp\alice, а Linux-сервер такого пользователя не знает. Делегирование (-K) с Windows на lnx1 не произойдёт по причине выше: клиент честно попросит, получит отказ своей же библиотеки, и в сеансе на lnx1 билета не будет. Включать LNX1$ неограниченное делегирование ради этого не стоит — тогда TGT каждого, кто входит на сервер, остаётся на нём; если на lnx1 нужен билет, проще kinit в сеансе.
Linux → Windows. ssh -K dc1.corp.example.com к Windows OpenSSH с включённым GSSAPI работает так же: SPN host/dc1.corp.example.com у контроллера есть всегда, а sshd Windows проверяет билет через SSPI.
Кеши и работа без пароля
На Linux TGT лежит в кеше учётных данных, и его тип задаётся в krb5.conf (default_ccache_name). Три варианта: FILE:/tmp/krb5cc_%{uid} — файл, классика, один на пользователя; KEYRING:persistent:%{uid} — в ключах ядра, не виден по файловой системе; KCM: — демон (у sssd это sssd-kcm), который хранит кеши для всех пользователей и умеет несколько кешей на пользователя. RHEL и Fedora с sssd используют KCM: пакет sssd-kcm кладёт default_ccache_name = KCM: в /etc/krb5.conf.d/. Ubuntu по умолчанию остаётся на файлах: при входе с паролем pam_sss кладёт TGT в /tmp/krb5cc_<uid>_<случайный суффикс> и выставляет в сеансе KRB5CCNAME, а без него библиотека ищет /tmp/krb5cc_<uid>. klist показывает текущий кеш, klist -A — все, kdestroy — удалить, KRB5CCNAME — переключить.
Билет появляется при входе с паролем через pam_sss и не появляется при входе по SSH-ключу или по GSSAPI без делегирования: пароля у sssd в этот момент нет, и TGT взять неоткуда. Это первая причина «я вошёл, а klist пуст» — как это выглядит, тоже в «Смотрим руками». Выход — kinit руками или вход с делегированием.
Службам, которым нужны свои билеты для планового обращения к каталогу или к SMB-ресурсу, пароль не нужен: ключ из keytab заменяет его:
$ sudo kinit -k -t /etc/krb5.keytab 'LNX1$@CORP.EXAMPLE.COM'
$ sudo klist
klist после этого покажет основным принципалом LNX1$@CORP.EXAMPLE.COM и один билет krbtgt/CORP.EXAMPLE.COM, выданный на срок по политике домена (по умолчанию 10 часов). Так резервное копирование по SMB или скрипт, читающий LDAP, работает от имени машины; в cron перед командой — kinit -k, или k5start для долгоживущих процессов, или gssproxy, который отдаёт службам билеты, не давая им keytab.
SPN и keytab для служб
Служба на Linux, в которую будут входить по Kerberos, должна иметь принципал класс/имя@REALM в каталоге и его ключ в keytab, доступном службе.
Для имени самой машины многие классы уже есть, хотя в servicePrincipalName их не видно. Атрибут sPNMappings объекта CN=Directory Service,CN=Windows NT,CN=Services в разделе конфигурации объявляет десятки классов синонимами host, и http среди них: на запрос HTTP/lnx1.corp.example.com KDC выдаст билет на ключ LNX1$, хотя у учётной записи записан только host/lnx1.corp.example.com. Для сайта под другим именем (intranet.corp.example.com) SPN нужен явный. Два способа.
На учётной записи машины. SPN добавляется к LNX1$, ключ — тот же, что у host/, и adcli заодно дописывает записи с новым именем в keytab:
$ sudo adcli update --add-service-principal=HTTP/intranet.corp.example.com --verbose
$ sudo klist -k | grep HTTP
klist -k после этого покажет HTTP/intranet.corp.example.com@CORP.EXAMPLE.COM с тем же KVNO, что у host/, по строке на каждый тип шифрования. SPN можно добавить и с Windows-стороны — setspn -S HTTP/intranet.corp.example.com lnx1, — но это меняет только каталог, записи в keytab так не появится; adcli делает и то, и другое.
Keytab машины принадлежит root; веб-серверу нужен отдельный файл с нужными ключами: ktutil с rkt /etc/krb5.keytab, delent лишних, wkt /etc/apache2/http.keytab, и права chown www-data, chmod 0400. У такой копии есть срок годности: sssd меняет пароль машины раз в 30 дней (ad_maximum_machine_account_password_age), KVNO в каталоге растёт, /etc/krb5.keytab обновляется, а копия — нет, и через месяц веб-сервер перестаёт расшифровывать билеты. Копию нужно пересобирать после каждой смены пароля; надёжнее отдать ключ службе через gssproxy или завести для неё отдельную учётную запись.
На отдельной учётной записи. Для служб, которые живут на нескольких узлах за балансировщиком (один SPN должен быть у одного принципала, а ключ — на всех узлах), или которым нужны собственные права, — учётная запись домена svc-intranet, и keytab для неё делается на Windows:
PS C:\> New-ADUser -Name svc-intranet -SamAccountName svc-intranet -UserPrincipalName HTTP/intranet.corp.example.com@CORP.EXAMPLE.COM -Enabled $true -AccountPassword (Read-Host -AsSecureString) -PasswordNeverExpires $true -KerberosEncryptionType AES128, AES256
PS C:\> ktpass -princ HTTP/intranet.corp.example.com@CORP.EXAMPLE.COM -mapuser CORP\svc-intranet -crypto AES256-SHA1 -ptype KRB5_NT_PRINCIPAL -pass * -out C:\Temp\intranet.keytab
ktpass регистрирует SPN, ставит UPN, меняет пароль учётной записи на тот, что указан в -pass (звёздочка — спросить), и пишет ключ в файл; номер версии ключа vno после этого увеличивается, и старые keytab с этой учётной записи перестают работать. Файл везут на узлы с теми же правами, что выше. На Linux то же умеет msktutil, создавая учётную запись и keytab сразу с нужной стороны.
Групповые управляемые учётные записи из части 13 sssd и adcli не поддерживают: для Linux-служб остаются машинный keytab и обычная учётная запись с длинным паролем и плановой сменой через ktpass или msktutil --auto-update.
Веб-приложение
Apache с модулем mod_auth_gssapi превращает любое приложение в приложение с единым входом: браузер пользователя домена предъявляет билет, Apache проверяет его keytab и передаёт приложению имя в REMOTE_USER.
$ sudo apt install libapache2-mod-auth-gssapi
$ sudo tee /etc/apache2/conf-available/intranet-auth.conf <<'EOF'
<Location />
AuthType GSSAPI
AuthName "Corp intranet"
GssapiCredStore keytab:/etc/apache2/http.keytab
GssapiBasicAuth Off
GssapiNegotiateOnce On
GssapiLocalName On
GssapiAllowedMech krb5
Require valid-user
</Location>
EOF
$ sudo a2enmod auth_gssapi && sudo a2enconf intranet-auth && sudo systemctl reload apache2
GssapiLocalName On — передавать alice, а не alice@CORP.EXAMPLE.COM; GssapiBasicAuth Off — не предлагать ввод пароля тем, у кого нет билета (с On Apache принял бы пароль и сам сходил бы за билетом — запасной путь для машин не из домена, но пароль тогда идёт через веб-сервер); GssapiNegotiateOnce On — не повторять вызов Negotiate, если первый токен не подошёл. Обмен по RFC 4559:
GET / HTTP/1.1
Host: intranet.corp.example.com
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Negotiate
GET / HTTP/1.1
Host: intranet.corp.example.com
Authorization: Negotiate <base64: токен SPNEGO с AP-REQ на HTTP/intranet.corp.example.com>
HTTP/1.1 200 OK
WWW-Authenticate: Negotiate <base64: ответ SPNEGO с AP-REP>
Первый ответ — вызов; второй запрос несёт SPNEGO-токен с AP-REQ на HTTP/intranet.corp.example.com; в ответе 200 — AP-REP для взаимной аутентификации. Пароля нет, формы входа нет. Реальный обмен с веб-сервером домена — в «Смотрим руками».
Браузер отправляет билет не любому сайту, а только тем, которым разрешено. Chrome и Edge на Windows без политики спрашивают зоны Windows: по умолчанию это сайты зоны «Местная интрасеть» — имена без точек и то, что добавлено политикой «Site to Zone Assignment List»; явный список задаёт политика AuthServerAllowlist (раньше AuthServerWhitelist). Chrome на Linux и macOS — та же политика или ключ --auth-server-allowlist=*.corp.example.com; Firefox — network.negotiate-auth.trusted-uris. Пока сайт не в списке, вместо единого входа пользователь видит окно ввода пароля или, если сервер предлагает только Negotiate, страницу 401. Делегирование из браузера (чтобы веб-приложение ходило дальше от имени пользователя) — отдельный список AuthNegotiateDelegateAllowlist, и по умолчанию оно выключено.
Имя, на которое браузер просит билет, — HTTP/<имя хоста>, класс всегда HTTP, независимо от TLS. Какое имя хоста, решает клиент: Chrome и Edge по умолчанию разворачивают CNAME и просят билет на каноническое имя (CNAME intranet → lnx1 даст запрос HTTP/lnx1.corp.example.com; политика DisableAuthNegotiateCnameLookup это выключает), а curl и многие другие клиенты — на имя из URL. Если запрошенного SPN нет ни у одной учётной записи, KDC отвечает KDC_ERR_S_PRINCIPAL_UNKNOWN (часть 6), клиент откатывается на NTLM, которого у Apache нет, — то есть просто 401. Надёжнее всего, когда сайт открывают по имени, на которое есть SPN, а keytab на сервере содержит ключ этой учётной записи. У nginx встроенного модуля нет; обычно ставят spnego-http-auth-nginx-module или выносят аутентификацию в прокси перед nginx.
Делегирование с Linux-стороны
Веб-приложение на lnx1 должно обратиться к SQL Server или к SMB-ресурсу от имени вошедшего пользователя. Неограниченное делегирование из браузера почти никогда не включают; правильный путь — ограниченное делегирование, и MIT Kerberos его умеет с версии 1.8 через расширения S4U2Self и S4U2Proxy из части 6. На стороне каталога — как для Windows:
PS C:\> Set-ADUser svc-intranet -Add @{ 'msDS-AllowedToDelegateTo' = 'MSSQLSvc/sql1.corp.example.com:1433', 'cifs/fs1.corp.example.com' }
PS C:\> Set-ADAccountControl svc-intranet -TrustedToAuthForDelegation $true # с переходом протокола, если пользователь пришёл не по Kerberos
На Linux у mod_auth_gssapi есть GssapiDelegCcacheDir — складывать делегированные (если браузер прислал) или полученные через S4U2Proxy (GssapiUseS4U2Proxy On) билеты в каталог, откуда приложение берёт их через KRB5CCNAME. Приложение на Python, Java или .NET на Linux получает билет на MSSQLSvc/sql1 от имени alice без её пароля и без её TGT — через собственный keytab svc-intranet. Делегирование на основе ресурса (msDS-AllowedToActOnBehalfOfOtherIdentity на sql1) работает так же и настраивается владельцем ресурса, как в части 14.
Другие службы
PostgreSQL. SPN postgres/db1.corp.example.com на учётной записи машины (adcli update --add-service-principal=postgres/db1.corp.example.com), keytab для пользователя postgres, в postgresql.conf — krb_server_keyfile = '/etc/postgresql/krb5.keytab', в pg_hba.conf — hostgssenc all all 192.168.0.0/16 gss include_realm=0 krb_realm=CORP.EXAMPLE.COM. Пользователь alice с TGT подключается psql -h db1.corp.example.com без пароля, и соединение ещё и зашифровано GSS (hostgssenc принимает только такие соединения). Имя роли в базе должно совпадать с именем пользователя без realm. С Windows — тот же psql или любой клиент с libpq, билет из сеанса.
NFS. SPN nfs/nfs1.corp.example.com на сервере, rpc.gssd на клиентах, экспорт с sec=krb5p: каждый пользователь обращается к файлам со своим билетом, сервер проверяет его и подставляет UID из sssd — в отличие от NFS с sec=sys, где сервер верит UID, который назвал клиент. krb5 — только аутентификация, krb5i — плюс целостность каждого запроса, krb5p — плюс шифрование. Windows Server и сам может быть таким сервером: Server for NFS принимает krb5, krb5i и krb5p и сопоставляет пользователей Windows с UID и GID через атрибуты каталога (uidNumber, gidNumber).
Samba. Файловый сервер на Linux с security = ads вступает в домен как член (через net ads join или совместно с sssd), получает SPN cifs/, и клиенты Windows открывают его папки с билетом, как папки srv1: для них разницы нет.
Когда снова спрашивают пароль
Главный инструмент на Linux-стороне — трассировка библиотеки MIT Kerberos:
$ KRB5_TRACE=/dev/stderr ssh -K lnx1.corp.example.com true
Переменная KRB5_TRACE заставляет любую программу, собранную с libkrb5, писать в указанный файл каждый шаг: какой кеш выбран и с каким принципалом, есть ли в нём TGT, какой SPN запрошен, к какому KDC (имя из SRV-записи и адрес) ушёл TGS-REQ, что вернулось, с каким типом шифрования сессионный ключ и сохранён ли билет в кеш. Ошибки, которые здесь видны, и что они значат:
| В трассировке или ответе | Причина |
|---|---|
Server not found in Kerberos database |
у учётной записи нет SPN с таким именем; часто библиотека канонизировала имя через DNS и запросила host/lnx1-old.corp… — лечится rdns = false и dns_canonicalize_hostname = fallback в [libdefaults] krb5.conf |
Wrong principal in request, KRB_AP_ERR_MODIFIED, Decrypt integrity check failed |
служба не может расшифровать билет: kvno в keytab отстал от каталога (пароль машины сменился, а keytab или его копия нет — adcli update), SPN на другой учётной записи, дубликат SPN (setspn -X) |
No credentials cache found, No Kerberos credentials available |
у пользователя нет TGT: вошёл по ключу или по GSSAPI без делегирования, KRB5CCNAME указывает не туда, или билет истёк |
Clock skew too great |
время (часть 12) |
KDC has no support for encryption type |
в keytab ключ RC4, а учётная запись допускает только AES, или наоборот: klist -k -e и msDS-SupportedEncryptionTypes |
Cannot find KDC for realm |
DNS: нет SRV _kerberos._tcp или dns_lookup_kdc = false без [realms] |
Permission denied (gssapi-with-mic) при живом билете |
sshd отверг: нет GSSAPIAuthentication yes, keytab недоступен (права), или принципал не сопоставился с локальным пользователем; в журнале успешного входа это строка Authorized to alice, krb5 principal alice@CORP.EXAMPLE.COM (krb5_kuserok), сопоставление делает модуль localauth sssd, для чужого realm нужен .k5login или auth_to_local |
Проверка SPN и keytab без приложения — kvno: он просит у KDC билет на указанный SPN и печатает его номер версии ключа, по строке host/lnx1.corp.example.com@CORP.EXAMPLE.COM: kvno = 2 на каждое имя. Сравнить с keytab службы — sudo klist -k /etc/apache2/http.keytab: если kvno в билете больше, чем в keytab, keytab устарел. Без TGT kvno сразу отказывает той же ошибкой, что и klist:
alice@lnx1:~$ kvno host/lnx3.corp.example.com
kvno: No credentials cache found (filename: /tmp/krb5cc_1838401104) while getting client principal name
Смотрим руками
Вход с рабочей станции ws1 на lnx1 встроенным клиентом Windows, в ~\.ssh\config для *.corp.example.com — GSSAPIAuthentication yes:
PS C:\> ssh -v -l alice lnx1.corp.example.com true 2>&1 | Select-String 'Authenticat|gssapi|delegat'
debug1: Authenticating to lnx1.corp.example.com:22 as 'alice'
debug1: get_agent_identities: ssh_get_authentication_socket: No such file or directory
debug1: Authentications that can continue: publickey,gssapi-keyex,gssapi-with-mic,password
debug1: Next authentication method: gssapi-with-mic
Authenticated to lnx1.corp.example.com ([192.168.1.60]:22) using "gssapi-with-mic".
Сервер предложил четыре метода, клиент сразу взял gssapi-with-mic и вошёл с первой попытки; строка про агента значит лишь, что ssh-agent на рабочей станции не запущен, — ключи не понадобились. gssapi-keyex — обмен ключами через GSSAPI из патча, который Ubuntu добавляет к OpenSSH; на сервере он выключен. В кеше Windows после входа — билет на host/lnx1, рядом для сравнения билет на LDAP контроллера:
PS C:\> klist | Select-String -Context 0,3 'host/lnx1|ldap/dc1\.corp\.example\.com @'
> Server: host/lnx1.corp.example.com @ CORP.EXAMPLE.COM
KerbTicket Encryption Type: AES-256-CTS-HMAC-SHA1-96
Ticket Flags 0x40a10000 -> forwardable renewable pre_authent name_canonicalize
Start Time: 10/30/2026 12:39:53 (local)
> Server: ldap/dc1.corp.example.com @ CORP.EXAMPLE.COM
KerbTicket Encryption Type: AES-256-CTS-HMAC-SHA1-96
Ticket Flags 0x40a50000 -> forwardable renewable pre_authent ok_as_delegate name_canonicalize
Start Time: 10/30/2026 10:25:26 (local)
У билета контроллера есть ok_as_delegate, у lnx1 — нет. Поэтому с -K клиент Windows просит делегирование и не получает его:
PS C:\> ssh -v -K -l alice lnx1.corp.example.com true 2>&1 | Select-String 'Authenticat|gssapi|delegat'
debug1: Authenticating to lnx1.corp.example.com:22 as 'alice'
debug1: get_agent_identities: ssh_get_authentication_socket: No such file or directory
debug1: Authentications that can continue: publickey,gssapi-keyex,gssapi-with-mic,password
debug1: Next authentication method: gssapi-with-mic
debug1: Delegating credentials
debug1: sspi delegation was requested but not fulfilled
debug1: Delegating credentials
debug1: sspi delegation was requested but not fulfilled
Authenticated to lnx1.corp.example.com ([192.168.1.60]:22) using "gssapi-with-mic".
Вход состоялся, TGT остался на ws1. В сеансе на lnx1 кеша нет вовсе:
PS C:\> ssh -l alice lnx1.corp.example.com klist
klist: No credentials cache found (filename: /tmp/krb5cc_1838401104)
То же самое после входа по ключу; после входа с паролем там был бы файл /tmp/krb5cc_1838401104_<суффикс> от pam_sss.
На lnx1 — действующие настройки sshd (а не то, что написано в файлах) и журнал:
alice@lnx1:~$ sudo sshd -T | grep -i gssapi
gssapiauthentication yes
gssapicleanupcredentials yes
gssapistrictacceptorcheck yes
gssapikeyexchange no
gssapistorecredentialsonrekey no
gssapikexalgorithms gss-group14-sha256-,gss-group16-sha512-,gss-nistp256-sha256-,gss-curve25519-sha256-
alice@lnx1:~$ sudo journalctl -u ssh --since -15min -o short-iso --no-pager | grep 'sshd-session\[537477\]' | grep -vi disconnect
2026-10-30T09:40:31+00:00 lnx1.corp.example.com sshd-session[537477]: Authorized to alice, krb5 principal alice@CORP.EXAMPLE.COM (krb5_kuserok)
2026-10-30T09:40:31+00:00 lnx1.corp.example.com sshd-session[537477]: Accepted gssapi-with-mic for alice from 192.168.1.50 port 62513 ssh2: alice@CORP.EXAMPLE.COM
2026-10-30T09:40:31+00:00 lnx1.corp.example.com sshd-session[537477]: pam_unix(sshd:session): session opened for user alice(uid=1838401104) by alice(uid=0)
2026-10-30T09:40:31+00:00 lnx1.corp.example.com sshd-session[537477]: pam_unix(sshd:session): session closed for user alice
Сначала sshd сопоставил принципал alice@CORP.EXAMPLE.COM с локальным alice (krb5_kuserok), потом принял вход и дописал принципал в конец строки Accepted. За месяц на этом сервере в журнале 101 вход gssapi-with-mic, 12 по ключу и 4 по паролю:
alice@lnx1:~$ sudo journalctl -u ssh --since -30days --no-pager | grep -oE 'Accepted [a-z-]+' | sort | uniq -c
101 Accepted gssapi-with-mic
4 Accepted password
12 Accepted publickey
Keytab, которым sshd расшифровал билет (-e — типы шифрования; сами ключи klist не печатает):
alice@lnx1:~$ sudo klist -k -e /etc/krb5.keytab
Keytab name: FILE:/etc/krb5.keytab
KVNO Principal
---- --------------------------------------------------------------------------
2 LNX1$@CORP.EXAMPLE.COM (aes128-cts-hmac-sha1-96)
2 LNX1$@CORP.EXAMPLE.COM (aes256-cts-hmac-sha1-96)
2 host/LNX1@CORP.EXAMPLE.COM (aes128-cts-hmac-sha1-96)
2 host/LNX1@CORP.EXAMPLE.COM (aes256-cts-hmac-sha1-96)
2 host/lnx1.corp.example.com@CORP.EXAMPLE.COM (aes128-cts-hmac-sha1-96)
2 host/lnx1.corp.example.com@CORP.EXAMPLE.COM (aes256-cts-hmac-sha1-96)
2 RestrictedKrbHost/LNX1@CORP.EXAMPLE.COM (aes128-cts-hmac-sha1-96)
2 RestrictedKrbHost/LNX1@CORP.EXAMPLE.COM (aes256-cts-hmac-sha1-96)
2 RestrictedKrbHost/lnx1.corp.example.com@CORP.EXAMPLE.COM (aes128-cts-hmac-sha1-96)
2 RestrictedKrbHost/lnx1.corp.example.com@CORP.EXAMPLE.COM (aes256-cts-hmac-sha1-96)
И та же учётная запись в каталоге — SPN, допустимые типы шифрования (24 = AES128 + AES256), номер версии ключа и доверие для делегирования:
PS C:\> Get-ADComputer lnx1 -Properties servicePrincipalName, msDS-SupportedEncryptionTypes, msDS-KeyVersionNumber, TrustedForDelegation |
Format-List Name, servicePrincipalName, msDS-SupportedEncryptionTypes, msDS-KeyVersionNumber, TrustedForDelegation
Name : LNX1
servicePrincipalName : {RestrictedKrbHost/lnx1.corp.example.com, RestrictedKrbHost/LNX1, host/lnx1.corp.example.com, host/LNX1}
msDS-SupportedEncryptionTypes : 24
msDS-KeyVersionNumber : 2
TrustedForDelegation : False
KVNO в каталоге и в keytab совпадают — 2, TrustedForDelegation — False, отсюда отказ в делегировании выше. Выдачу билета видно на контроллере в событии 4769; поле ServiceName в нём — учётная запись службы, а не SPN:
PS C:\> Invoke-Command -ComputerName dc1 -ScriptBlock {
Get-WinEvent -FilterHashtable @{LogName='Security'; Id=4769; StartTime=(Get-Date).AddHours(-2)} |
Where-Object { $_.Properties[2].Value -eq 'LNX1$' } | Select-Object -First 1 | ForEach-Object {
([xml]$_.ToXml()).Event.EventData.Data |
Where-Object Name -in 'ServiceName','TicketOptions','TicketEncryptionType','Status','TransmittedServices','ServiceSupportedEncryptionTypes','ServiceAvailableKeys' |
ForEach-Object { '{0} = {1}' -f $_.Name, $_.'#text' }
}
}
ServiceName = LNX1$
TicketOptions = 0x40810000
TicketEncryptionType = 0x12
Status = 0x0
TransmittedServices = -
ServiceSupportedEncryptionTypes = 0x18 (AES128-SHA96, AES256-SHA96)
ServiceAvailableKeys = RC4, AES128-SHA96, AES256-SHA96, AES128-SHA256, AES256-SHA384
Билет зашифрован AES256 (0x12), TransmittedServices пусто — это прямой запрос, не S4U. Если бы пользователь ушёл с lnx1 дальше по делегированному билету, в событии 4769 на cifs/fs1 адресом клиента был бы lnx1, а не её рабочая станция: по этому признаку делегирование видно в аудите.
Последнее — HTTP с Negotiate. Веб-приложение web1 на IIS с Windows-аутентификацией; запрос без учётных данных получает список методов:
PS C:\> curl.exe -s -v -o NUL https://web1.corp.example.com/ 2>&1 | Select-String '^[<>] (GET|HTTP|Authorization|WWW-Authenticate|Location)'
> GET / HTTP/1.1
< HTTP/1.1 401 Unauthorized
< WWW-Authenticate: Bearer
< WWW-Authenticate: Basic realm="https://web1.corp.example.com/"
< WWW-Authenticate: Negotiate
< WWW-Authenticate: NTLM
С --negotiate -u : (пустые учётные данные — сигнал брать билет из сеанса) curl, которому назван единственный метод, отправляет токен сразу, не дожидаясь 401; токены ниже обрезаны:
PS C:\> curl.exe -s -v -o NUL --negotiate -u : https://web1.corp.example.com/ 2>&1 | Select-String '^[<>] (GET|HTTP|Authorization|WWW-Authenticate|Location)'
> GET / HTTP/1.1
> Authorization: Negotiate YIIHuwYGKwYBBQUCoIIHrzCC…
< HTTP/1.1 302 Found
< Location: https://web1.corp.example.com/home/
< WWW-Authenticate: Negotiate oYG2MIGzoAMKAQChCwYJKoZI…
YII… — начало токена SPNEGO NegTokenInit (за ним OID SPNEGO 1.3.6.1.5.5.2 и AP-REQ внутри), около двух килобайт вместе с PAC; ответ oYG… — NegTokenResp с состоянием «принято» и AP-REP. Приложение ответило перенаправлением, а не 401: пользователь опознан. В кеше ws1 появился билет на HTTP/web1:
PS C:\> klist | Select-String -Context 0,3 'HTTP/'
> Server: HTTP/web1.corp.example.com @ CORP.EXAMPLE.COM
KerbTicket Encryption Type: AES-256-CTS-HMAC-SHA1-96
Ticket Flags 0x40a10000 -> forwardable renewable pre_authent name_canonicalize
Start Time: 10/30/2026 12:43:59 (local)
хотя SPN HTTP/… у web1 в каталоге нет:
PS C:\> (Get-ADComputer web1 -Properties servicePrincipalName).servicePrincipalName
TERMSRV/web1.corp.example.com
WSMAN/web1.corp.example.com
RestrictedKrbHost/web1.corp.example.com
HOST/web1.corp.example.com
TERMSRV/WEB1
WSMAN/WEB1
RestrictedKrbHost/WEB1
HOST/WEB1
Это и есть sPNMappings: http — синоним host, и KDC нашёл учётную запись по HOST/web1.corp.example.com.
Источники
GSSAPI, SPNEGO и протоколы:
- RFC 2743 — GSSAPI: контексты безопасности и токены, которые приложение переносит само;
- RFC 4121 — механизм Kerberos V5 для GSSAPI;
- RFC 4178 — SPNEGO: согласование механизма, NegTokenInit и NegTokenResp;
- RFC 4462 — аутентификация SSH через GSSAPI, метод
gssapi-with-micи обмен ключамиgssapi-keyex; - RFC 4559 — HTTP
Negotiate:WWW-AuthenticateиAuthorizationс токеном SPNEGO; - RFC 4120 — Kerberos V5: флаги билетов
forwardable,forwardedиok-as-delegate; - [MS-KILE]: Request Flags Details и Ticket Flag Details — клиент Windows пересылает TGT только серверу с
OK-AS-DELEGATE, KDC ставит флаг учётным записям с доверием для делегирования.
MIT Kerberos:
- krb5.conf —
default_ccache_name,rdns,dns_canonicalize_hostname,enforce_ok_as_delegate,dns_lookup_kdc, модулиlocalauth; - Credential cache — типы кешей FILE, DIR, KEYRING, KCM и переменная
KRB5CCNAME; - keytab — что хранит keytab и как с ним работает
ktutil; - kinit, klist и kvno —
kinit -k, флаги билетов вklist -f,klist -k -e, номер версии ключа; - .k5login — какие принципалы могут входить под локальным пользователем;
- Troubleshooting — трассировка
KRB5_TRACE; - krb5-1.8 — S4U2Self и S4U2Proxy в MIT Kerberos.
OpenSSH:
- sshd_config —
GSSAPIAuthentication,GSSAPICleanupCredentials,GSSAPIStrictAcceptorCheck; - ssh_config и ssh —
GSSAPIDelegateCredentials,PreferredAuthentications, ключ-K; - OpenSSH Server configuration for Windows —
sshdв Windows и его настройки.
SSSD и учётная запись машины:
- sssd-krb5 —
krb5_ccachedir,krb5_ccname_template, кеш по умолчанию изkrb5.conf; - sssd-ad —
ad_maximum_machine_account_password_age: смена пароля машины раз в 30 дней; - sssd-kcm — демон KCM и
default_ccache_name = KCM:; - adcli —
adcli update,--add-service-principal, keytab машины; - msktutil — учётные записи служб и keytab с Linux-стороны,
--auto-update; - gssproxy — доступ служб к ключам без чтения keytab;
- kstart —
k5startдля долгоживущих процессов.
Каталог:
- setspn и ktpass — SPN, UPN, смена пароля и файл keytab;
- SPN-Mappings attribute и [MS-DRSR]: MapSPN — синонимы класса
host; - Kerberos constrained delegation overview — ограниченное делегирование и делегирование на основе ресурса;
- 4769(S, F): A Kerberos service ticket was requested — поля события,
TicketOptions,TransmittedServices.
Веб:
- mod_auth_gssapi — директивы
GssapiCredStore,GssapiBasicAuth,GssapiLocalName,GssapiDelegCcacheDir,GssapiUseS4U2Proxy; - HTTP authentication в Chromium — список разрешённых серверов, зоны Windows, CNAME и SPN, делегирование;
- AuthServerAllowlist — политика Edge;
- Firefox: Authentication — список сайтов для SPNEGO в политиках Firefox;
- curl --negotiate — SPNEGO в curl.
Другие службы:
- PostgreSQL: GSSAPI Authentication и pg_hba.conf —
gss,hostgssenc,include_realm,krb_server_keyfile; - exports(5) и rpc.gssd(8) —
sec=krb5,krb5i,krb5pи клиентская служба RPCSEC_GSS; - NFS overview — Server for NFS в Windows Server, Kerberos и сопоставление пользователей;
- smb.conf и smbclient —
security = ads,--use-kerberos.
Один каталог, одна аутентификация для Windows, Linux, Wi-Fi, VPN и веб-приложений. Остался последний рубеж, который у большинства организаций давно существует рядом с доменом и живёт по своим правилам: облако.
Дальше — часть 31: Entra Connect. Что такое Entra ID рядом с Active Directory и зачем их связывать; что именно синхронизируется и что нет; три способа аутентификации — синхронизация хешей паролей, сквозная аутентификация и федерация; UPN и подтверждённые домены; фильтрация по OU и атрибутам; установка Entra Connect и учётная запись синхронизации; и Seamless SSO — Kerberos для облака.
Оставить комментарий могут только зарегистрированные пользователи.
Войдите на сайт или зарегистрируйтесь, чтобы оставить комментарий.