Windows Server для самых маленьких. Часть 29: Вводим Linux в домен

Все части серии «Windows Server для самых маленьких»

Во всех предыдущих частях Linux упоминался как клиент DNS, как цель для ssh и как машина, которой без Kerberos остаются только небезопасные обновления DNS. Пора ввести его в домен по-настоящему: чтобы пользователь alice входила на lnx1 своим доменным паролем, состояла там в своих группах, получала sudo по членству в группе каталога, и чтобы всё это работало по Kerberos без единого локального пароля. У Linux нет Netlogon, SYSVOL и групповых политик в привычном виде, поэтому «член домена» здесь означает другое, и это другое стоит разобрать по компонентам, прежде чем выполнять realm join.

Дистрибутив в примерах — Ubuntu 26.04 LTS; где Ubuntu 24.04 ведёт себя иначе, это сказано отдельно. На RHEL и его производных пакеты и файлы те же, с поправкой на dnf и authselect вместо pam-auth-update.


Из чего состоит членство

На Windows вступление в домен делает одна служба. На Linux это пять компонентов с разделённой ответственностью:

 ┌──────────────────────────────────────────────────────────────────────────┐
 │ realmd      обнаружение домена и вступление: находит контроллеры через   │
 │             DNS, вызывает adcli, пишет sssd.conf и krb5.conf             │
 ├──────────────────────────────────────────────────────────────────────────┤
 │ adcli       создаёт учётную запись компьютера в каталоге, получает ключи │
 │             и пишет их в /etc/krb5.keytab; по вызову sssd меняет пароль  │
 │             машины                                                       │
 ├──────────────────────────────────────────────────────────────────────────┤
 │ sssd        демон, который делает всё в рабочем режиме: ищет контроллеры │
 │             по сайту, читает пользователей и группы по LDAP с Kerberos,  │
 │             проверяет пароли по Kerberos, кеширует, обновляет DNS,       │
 │             применяет права входа из GPO                                 │
 ├──────────────────────────────────────────────────────────────────────────┤
 │ NSS         libnss_sss: getpwnam("alice") → sssd → каталог: пользователи │
 │             и группы домена видны системе как обычные                    │
 ├──────────────────────────────────────────────────────────────────────────┤
 │ PAM         pam_sss: аутентификация (Kerberos AS-REQ), авторизация       │
 │             (можно ли этому пользователю на эту машину), смена пароля;   │
 │             домашний каталог при первом входе создаёт pam_mkhomedir      │
 └──────────────────────────────────────────────────────────────────────────┘

Keytab — файл с долговременными ключами принципалов машины: LNX1$@CORP.EXAMPLE.COM, host/lnx1.corp.example.com@CORP.EXAMPLE.COM и ещё нескольких. Это тот же секрет, что пароль учётной записи компьютера на Windows (часть 6), только в виде файла. Всё, что sssd делает от имени машины, — LDAP-запросы, обновления DNS — он делает билетами, полученными этими ключами, и входит при этом как LNX1$@CORP.EXAMPLE.COM: TGT в AD выдаётся только на имя учётной записи, а host/… — имя службы, к которой приходят чужие билеты. Альтернатива sssd — Samba winbind, демон из состава Samba, — даёт то же самое и нужна, когда машина ещё и файловый сервер SMB (протокол общих папок Windows, часть 3); для серверов приложений стандарт — sssd.


Подготовка

Три вещи, без которых вступление пройдёт, а Kerberos — нет.

Имя. Полное, совпадающее с будущей DNS-записью:

$ sudo hostnamectl set-hostname lnx1.corp.example.com
$ hostname; hostname -f; hostname -s
lnx1.corp.example.com
lnx1.corp.example.com
lnx1

/etc/hosts. Установщик Ubuntu пишет в него строку с коротким именем:

$ grep 127.0.1.1 /etc/hosts
127.0.1.1 lnx1
$ getent hosts lnx1.corp.example.com
192.168.1.60    lnx1.corp.example.com

Пока имя машины полное, эта строка безвредна: hostname -f ищет lnx1.corp.example.com, в /etc/hosts его нет, и полное имя приходит из DNS. Ломается сочетание короткого имени машины и такой строки: тогда hostname -f находит lnx1 в /etc/hosts и возвращает его без домена. adcli берёт полное имя так же — gethostname(), а если оно короткое, то getaddrinfo() с запросом канонического имени, — и зарегистрирует dNSHostName и SPN без домена; sssd по тому же имени ищет свой принципал в keytab и регистрирует запись в DNS. Если короткое имя машины всё же нужно, строку пишут так, чтобы полное имя стояло первым: 192.168.1.60 lnx1.corp.example.com lnx1. Так и советует документация Debian для машины с постоянным адресом и именем в DNS: вместо 127.0.1.1 — постоянный адрес, вместо короткого имени — полное.

DNS и время. DNS-серверы — контроллеры, через systemd-resolved или /etc/resolv.conf:

$ resolvectl status eth0
Link 2 (eth0)
    Current Scopes: DNS
         Protocols: +DefaultRoute -LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported
Current DNS Server: 192.168.1.11
       DNS Servers: 192.168.1.10 192.168.1.11
        DNS Domain: corp.example.com
     Default Route: yes

Время — тоже с контроллеров, потому что пять минут допустимого расхождения Kerberos действуют и здесь (часть 12). Ubuntu 26.04 ставит chrony — клиент и сервер NTP (протокол синхронизации времени по UDP 123) — и берёт время из пулов Canonical через NTS (Network Time Security, RFC 8915: ключи для подписи пакетов NTP согласуются по TLS):

$ systemctl is-active chrony systemd-timesyncd
active
inactive
$ ls /etc/chrony/sources.d/
README  ubuntu-ntp-pools.sources
$ grep -h '^pool' /etc/chrony/sources.d/*
pool 1.ntp.ubuntu.com iburst maxsources 1 nts prefer
pool 2.ntp.ubuntu.com iburst maxsources 1 nts prefer
pool 3.ntp.ubuntu.com iburst maxsources 1 nts prefer
pool 4.ntp.ubuntu.com iburst maxsources 1 nts prefer
pool ntp-bootstrap.ubuntu.com iburst maxsources 1 nts certset 1

Контроллеры отвечают по NTP без подписи всем (часть 12), NTS они не умеют, поэтому источники заменяют обычными строками server:

$ sudo rm /etc/chrony/sources.d/ubuntu-ntp-pools.sources
$ sudo tee /etc/chrony/sources.d/corp.sources > /dev/null <<'EOF'
server dc1.corp.example.com iburst
server dc2.corp.example.com iburst
EOF
$ sudo chronyc reload sources

В Ubuntu 24.04 вместо chrony работает systemd-timesyncd с ntp.ubuntu.com; контроллеры для него задаются строкой NTP= в /etc/systemd/timesyncd.conf.

Пакеты:

$ sudo apt install realmd sssd sssd-tools adcli krb5-user libnss-sss libpam-sss oddjob oddjob-mkhomedir packagekit samba-common-bin

krb5-user даёт kinit, klist и kvno и при установке спросит realm по умолчанию — CORP.EXAMPLE.COM, заглавными; сам realmd этот пакет не требует, и без него на машине нет klist. samba-common-bin нужен ради net ads для редких операций; packagekit — чтобы realmd сам доставлял недостающее.


Вступление

Сначала посмотреть, что realmd нашёл:

$ realm discover -v corp.example.com
 * Resolving: _ldap._tcp.corp.example.com
 * Performing LDAP DSE lookup on: 192.168.1.10
 * Performing LDAP DSE lookup on: 192.168.1.11
 * Successfully discovered: corp.example.com
corp.example.com
  type: kerberos
  realm-name: CORP.EXAMPLE.COM
  domain-name: corp.example.com
  configured: kerberos-member
  server-software: active-directory
  client-software: sssd
  required-package: sssd-tools
  required-package: sssd
  required-package: libnss-sss
  required-package: libpam-sss
  required-package: adcli
  required-package: samba-common-bin
  login-formats: %U
  login-policy: allow-realm-logins

Это вывод с машины, которая уже в домене; до вступления в поле configured стоит no, а строк login-formats и login-policy нет. Обнаружение — SRV-запись _ldap._tcp.corp.example.com (запись DNS, которая называет узлы и порты службы, RFC 2782; контроллеры регистрируют её сами, часть 3), затем чтение RootDSE каждого найденного контроллера (часть 5) и LDAP ping — поиск атрибута Netlogon в RootDSE, на который контроллер отвечает своими флагами и сайтом; realmd делает его по TCP. Вступление:

$ sudo realm join -v -U alice.admin corp.example.com
 * Resolving: _ldap._tcp.corp.example.com
 * Performing LDAP DSE lookup on: 192.168.1.10
 * Performing LDAP DSE lookup on: 192.168.1.11
 * Successfully discovered: corp.example.com
Password for alice.admin:
 * Unconditionally checking packages
 * Resolving required packages
 * LANG=C /usr/sbin/adcli join --verbose --domain corp.example.com --domain-realm CORP.EXAMPLE.COM --domain-controller 192.168.1.10 --login-type user --login-user alice.admin --stdin-password
 * Using domain name: corp.example.com
 * Calculated computer account name from fqdn: LNX1
 * Using domain realm: corp.example.com
 * Sending NetLogon ping to domain controller: 192.168.1.10
 * Received NetLogon info from: dc1.corp.example.com
 * Wrote out krb5.conf snippet to /var/cache/realmd/adcli-krb5-bZXCpu/krb5.d/adcli-krb5-conf-C1KYf8
 * Authenticated as user: alice.admin@CORP.EXAMPLE.COM
 * Using GSS-SPNEGO for SASL bind
 * Looked up short domain name: CORP
 * Looked up domain SID: S-1-5-21-3623811015-3361044348-30300820
 * Received NetLogon info from: dc1.corp.example.com
 * Using fully qualified name: lnx1.corp.example.com
 * Using domain name: corp.example.com
 * Using computer account name: LNX1
 * Using domain realm: corp.example.com
 * Calculated computer account name from fqdn: LNX1
 * Generated 120 character computer password
 * Using keytab: FILE:/etc/krb5.keytab
 * A computer account for LNX1$ does not exist
 * Found well known computer container at: CN=Computers,DC=corp,DC=example,DC=com
 * Calculated computer account: CN=LNX1,CN=Computers,DC=corp,DC=example,DC=com
 * Encryption type [16] not permitted.
 * Encryption type [23] not permitted.
 * Encryption type [3] not permitted.
 * Encryption type [1] not permitted.
 * Created computer account: CN=LNX1,CN=Computers,DC=corp,DC=example,DC=com
 * Trying to set computer password with Kerberos
 * Set computer password
 * Retrieved kvno '2' for computer account in directory: CN=LNX1,CN=Computers,DC=corp,DC=example,DC=com
 * Checking RestrictedKrbHost/lnx1.corp.example.com
 *    Added RestrictedKrbHost/lnx1.corp.example.com
 * Checking RestrictedKrbHost/LNX1
 *    Added RestrictedKrbHost/LNX1
 * Checking host/lnx1.corp.example.com
 *    Added host/lnx1.corp.example.com
 * Checking host/LNX1
 *    Added host/LNX1
 ! Couldn't authenticate with keytab while discovering which salt to use: LNX1$@CORP.EXAMPLE.COM: Client 'LNX1$@CORP.EXAMPLE.COM' not found in Kerberos database
 * Added the entries to the keytab: LNX1$@CORP.EXAMPLE.COM: FILE:/etc/krb5.keytab
 * Added the entries to the keytab: host/LNX1@CORP.EXAMPLE.COM: FILE:/etc/krb5.keytab
 * Added the entries to the keytab: host/lnx1.corp.example.com@CORP.EXAMPLE.COM: FILE:/etc/krb5.keytab
 * Added the entries to the keytab: RestrictedKrbHost/LNX1@CORP.EXAMPLE.COM: FILE:/etc/krb5.keytab
 * Added the entries to the keytab: RestrictedKrbHost/lnx1.corp.example.com@CORP.EXAMPLE.COM: FILE:/etc/krb5.keytab
 * /usr/sbin/update-rc.d sssd enable
 * /usr/sbin/service sssd restart
 * Successfully enrolled machine in realm

Подробный вывод (-v) показывает, что произошло:

  • adcli получил TGT для alice.admin и привязался к LDAP контроллера через SASL (RFC 4422 — способ подключить к протоколу сменный механизм аутентификации) с механизмом GSS-SPNEGO: SPNEGO (RFC 4178) договаривается о механизме GSS-API, и здесь это Kerberos, так что пароль alice.admin по LDAP не передаётся;
  • создал учётную запись LNX1$ в CN=Computers и задал ей случайный пароль из 120 символов протоколом смены пароля Kerberos (RFC 3244, порт 464): новый пароль едет к KDC, зашифрованный ключом сеанса билета;
  • строки Encryption type [n] not permitted — типы шифрования, которые библиотека Kerberos не разрешает: DES (1, 3), 3DES (16) и RC4 (23); в сборке MIT Kerberos в Ubuntu 26.04 RC4 из списка по умолчанию убран, поэтому ключи — только AES;
  • зарегистрировал SPN host/ и RestrictedKrbHost/ на полное и короткое имя и записал ключи в keytab;
  • предупреждение ! про соль — adcli хотел проверить, с какой солью (строкой, которая подмешивается к паролю при вычислении ключа) KDC считает ключи, но запрос ушёл к контроллеру, который новую учётную запись ещё не получил репликацией; ключи записаны без проверки, и на работе машины это не сказалось.

С --computer-ou='OU=Linux,OU=Servers,DC=corp,DC=example,DC=com' учётная запись создаётся сразу в нужном OU, а --os-name и --os-version заполняют атрибуты операционной системы — без них adcli пишет в operatingSystem значение, заданное при сборке, и оставляет версию пустой. Со стороны каталога это обычный компьютер:

PS C:\> Get-ADComputer lnx1 -Properties OperatingSystem, OperatingSystemVersion, dNSHostName, servicePrincipalName, msDS-SupportedEncryptionTypes, msDS-KeyVersionNumber, PasswordLastSet, userAccountControl | Format-List Name, OperatingSystem, OperatingSystemVersion, dNSHostName, servicePrincipalName, msDS-SupportedEncryptionTypes, msDS-KeyVersionNumber, userAccountControl, PasswordLastSet

Name                          : LNX1
OperatingSystem               : pc-linux-gnu
OperatingSystemVersion        :
dNSHostName                   : lnx1.corp.example.com
servicePrincipalName          : {RestrictedKrbHost/lnx1.corp.example.com, RestrictedKrbHost/LNX1, host/lnx1.corp.example.com, host/LNX1}
msDS-SupportedEncryptionTypes : 24
msDS-KeyVersionNumber         : 2
userAccountControl            : 69632
PasswordLastSet               : 19.10.2026 17:38:49

msDS-SupportedEncryptionTypes = 24 — AES128 и AES256 (биты 0x8 и 0x10), то есть только то, что разрешила библиотека. userAccountControl = 69632 — это 0x11000: учётная запись рабочей станции (0x1000) и «пароль не истекает» (0x10000), как у Windows-клиентов; меняет пароль сама машина. Keytab:

$ sudo klist -k -e
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)

KVNO 2 — тот же номер версии ключа, что в msDS-KeyVersionNumber; при смене пароля машины он вырастет и в каталоге, и здесь. Ключи в keytab для всех принципалов одни и те же — это ключи одной учётной записи под разными именами.

В Ubuntu 24.04 картина другая: MIT Kerberos 1.20 там разрешает RC4, и adcli записывает каждому принципалу три ключа — плюс arcfour-hmac, — а в msDS-SupportedEncryptionTypes ставит 28 (0x4 + 0x8 + 0x10). Через месяц работы в keytab такой машины лежат два поколения ключей, например KVNO 6 и 7: при смене пароля adcli оставляет предыдущий ключ, потому что новый ещё не на всех контроллерах и билеты, выданные со старым, должны продолжать расшифровываться. Как убрать RC4 у такой машины — в постскриптуме.


krb5.conf

Библиотеке Kerberos нужно знать realm по умолчанию и где искать KDC. В Ubuntu 24.04 без krb5-user файл /etc/krb5.conf целиком пишет realmd:

$ cat /etc/krb5.conf

[libdefaults]
udp_preference_limit = 0
default_realm = CORP.EXAMPLE.COM

udp_preference_limit = 0 заставляет начинать с TCP: билеты AD с PAC и группами часто не помещаются в датаграмму UDP (часть 6). В Ubuntu 26.04 вместе с krb5-user ставится krb5-config, который создаёт большой файл-образец с чужими realm'ами для примера; realmd дописывает в него те же две строки, а первой строкой файла стоит includedir /etc/krb5.conf.d/. Через неё подключается то, что пишет sssd:

$ grep -v '^#' /etc/krb5.conf.d/enable_sssd_conf_dir
includedir /var/lib/sss/pubconf/krb5.include.d/
$ ls /var/lib/sss/pubconf/krb5.include.d/
domain_realm_corp_example_com  krb5_libdefaults  localauth_plugin
$ cat /var/lib/sss/pubconf/krb5.include.d/domain_realm_corp_example_com
[domain_realm]
.corp.example.com = CORP.EXAMPLE.COM
corp.example.com = CORP.EXAMPLE.COM

[domain_realm] сопоставляет имена узлов с realm; localauth_plugin подключает модуль sssd, который переводит принципал в имя локального пользователя. Без него действует правило по умолчанию: принципал из одного компонента в realm по умолчанию превращается в то же имя, alice@CORP.EXAMPLE.COM — в alice. Этого хватает, пока пользователи называются коротко; для полных имён и пользователей других доменов нужен модуль sssd. В том коротком krb5.conf, который realmd пишет на 24.04, строки includedir нет, и эти фрагменты там не читаются.

Списка KDC нет ни в одном из файлов: адреса контроллеров, с которыми сейчас работает sssd, он кладёт в /var/lib/sss/pubconf/kdcinfo.CORP.EXAMPLE.COM, а модуль sssd_krb5_locator_plugin, который libkrb5 загружает сама, отдаёт их kinit и всем остальным программам.


sssd.conf

realmd написал /etc/sssd/sssd.conf, и его стоит прочитать:

[sssd]
domains = corp.example.com
config_file_version = 2
services = nss, pam

[domain/corp.example.com]
default_shell = /bin/bash
krb5_store_password_if_offline = True
cache_credentials = True
krb5_realm = CORP.EXAMPLE.COM
realmd_tags = manages-system joined-with-adcli
id_provider = ad
fallback_homedir = /home/%u@%d
ad_domain = corp.example.com
use_fully_qualified_names = False
ldap_id_mapping = True
access_provider = ad

Что здесь важно:

  • id_provider = ad — пользователи и группы из каталога. Контроллеры sssd находит сам по SRV-записям с учётом сайта, как DC Locator, и переключается на другой при отказе; ad_server задают только когда нужно принудительно. Какой контроллер и сайт он выбрал бы сейчас, показывает adcli info:

    $ adcli info corp.example.com
    [domain]
    domain-name = corp.example.com
    domain-short = CORP
    domain-forest = corp.example.com
    domain-controller = dc1.corp.example.com
    domain-controller-site = Office
    domain-controller-flags = gc ldap ds kdc timeserv closest writable full-secret ads-web
    domain-controller-usable = yes
    domain-controllers = dc1.corp.example.com dc2.corp.example.com dc3.corp.example.com
    [computer]
    computer-site = Office
    
  • ldap_id_mapping = True — UID и GID вычисляются из SID. Диапазон от 200000 до 2000200000 разбит на 10000 срезов по 200000 номеров; SID домена пропускается через хеш murmurhash3, остаток от деления на число срезов выбирает срез домена, а RID — смещение в нём. Результат одинаков на всех машинах с sssd и этим доменом, но с Windows не связан, а uidNumber и gidNumber из каталога при этом игнорируются. Альтернатива — хранить их в каталоге (атрибуты RFC 2307, который описывает, как хранить учётные записи UNIX в LDAP; они есть в схеме AD) и поставить ldap_id_mapping = False, при вступлении — realm join --automatic-id-mapping=no. Это нужно для NFS (сетевой файловой системы UNIX, которая проверяет права по числовым UID и GID) и для совместимости с существующими UID, и требует заполнять атрибуты каждому пользователю и группе.

  • use_fully_qualified_names — по умолчанию realmd пишет True, и пользователь называется alice@corp.example.com. Здесь стоит False: короткие имена (fully-qualified-names = no в /etc/realmd.conf до вступления или правка этой строки), и realm list показывает login-formats: %U. Удобно в одном домене; при нескольких доменах с совпадающими именами нужны полные.

  • fallback_homedir = /home/%u@%d — домашний каталог, если в каталоге не задан unixHomeDirectory; так по умолчанию делает realmd, и у alice это /home/alice@corp.example.com. Привычное /home/%u задаётся здесь же. Создаёт каталог pam_mkhomedir при первом входе (sudo pam-auth-update --enable mkhomedir).

  • cache_credentials = True — работа без связи с контроллером: sssd хранит солёный хеш SHA-512 пароля последнего успешного входа и пускает по нему, когда контроллер недоступен (срок не ограничен, пока не задан offline_credentials_expiration). krb5_store_password_if_offline — запомнить пароль, введённый без связи, и получить по нему билет, когда связь вернётся; пароль до тех пор лежит открытым текстом в связке ключей ядра.

  • access_provider = ad — учитывать отключённые и просроченные учётные записи и, через ad_gpo_access_control, права входа из групповых политик (часть 24). Режим по умолчанию — enforcing. sssd читает из SYSVOL политики, которые применяются к компьютеру, берёт из их параметров безопасности права входа и сопоставляет их со службами PAM: sshd и cockpit — с «Allow log on through Remote Desktop Services» и «Deny log on through Remote Desktop Services», login, su и графические входы — с «Allow log on locally» и «Deny log on locally». Если ни одна политика право не задаёт, вход разрешён; если задаёт — разрешён только перечисленным, а запрет сильнее разрешения. Встроенные группы вроде Administrators (S-1-5-32-544) sssd в этих правилах не учитывает, так что разрешать нужно доменной группе. Службы PAM, которых нет ни в одном списке, по умолчанию получают отказ (ad_gpo_default_right = deny). permissive вычисляет решение и пишет в журнал, но не отказывает; disabled выключает проверку.

  • dyndns_update — в провайдере ad включено по умолчанию, поэтому в файле его нет: при подключении и затем раз в сутки (dyndns_refresh_interval = 86400) sssd регистрирует A и PTR утилитой nsupdate с подписью GSS-TSIG от имени LNX1$, как клиент Windows в части 7; владельцем созданной записи становится LNX1$.

  • ad_maximum_machine_account_password_age — тоже умолчание, 30 дней: раз в сутки sssd проверяет возраст пароля машины и, если он старше, меняет его через adcli, как Netlogon у Windows; 0 отключает.

После правки — sudo systemctl restart sssd, а перед этим — проверка синтаксиса:

$ sudo sssctl config-check
Issues identified by validators: 0

Messages generated during configuration merging: 0

Used configuration snippet files: 0

Проверка, что sssd видит домен и пользователей:

$ sudo sssctl domain-status corp.example.com
Online status: Online

Active servers:
AD Global Catalog: dc1.corp.example.com
AD Domain Controller: dc2.corp.example.com

Discovered AD Global Catalog servers:
- dc1.corp.example.com
- dc2.corp.example.com
- dc3.corp.example.com

Discovered AD Domain Controller servers:
- dc2.corp.example.com
- dc1.corp.example.com
- dc3.corp.example.com

$ getent passwd alice
alice:*:1565001104:1565000513:Alice Smith:/home/alice@corp.example.com:/bin/bash
$ id alice
uid=1565001104(alice) gid=1565000513(domain users) groups=1565000513(domain users),1565001145(linux-admins)
$ getent group linux-admins
linux-admins:*:1565001145:alice,bob

UID 1565001104 — начало среза этого домена 1565000000 плюс RID 1104 из части 3; GID 1565000513 — Domain Users с RID 513. Имена групп sssd отдаёт строчными: провайдер ad не различает регистр, как и сам каталог. Группы с пробелами в именах — domain users — остаются группами с пробелами, и в sudoers их придётся экранировать.

Как эти пользователи попадают в систему, видно в nsswitch.conf и стеке PAM — их правит пакет libnss-sss/libpam-sss при установке:

$ grep -E '^(passwd|group|shadow|services|netgroup|automount):' /etc/nsswitch.conf
passwd:         files systemd sss
group:          files systemd sss
shadow:         files systemd sss
services:       db files sss
netgroup:       nis sss
automount:  sss
$ grep -h -E 'pam_(unix|sss|mkhomedir|localuser)' /etc/pam.d/common-auth /etc/pam.d/common-account /etc/pam.d/common-session
auth    [success=2 default=ignore]      pam_unix.so nullok
auth    [success=1 default=ignore]      pam_sss.so use_first_pass
account [success=1 new_authtok_reqd=done default=ignore]        pam_unix.so
account sufficient                      pam_localuser.so
account [default=bad success=ok user_unknown=ignore]    pam_sss.so
session required                        pam_mkhomedir.so umask=0022 skel=/etc/skel
session required        pam_unix.so
session optional                        pam_sss.so

Сначала локальные файлы, потом sssd: локальный alice из /etc/passwd перекроет доменную. В PAM пароль сначала проверяет pam_unix по /etc/shadow, а если пользователь не локальный — pam_sss с тем же паролем (use_first_pass); в account локальные пользователи проходят по pam_localuser, не спрашивая sssd.


Вход и доступ

ssh alice@lnx1 с паролем (со встроенного клиента Windows — ssh -l alice lnx1.corp.example.com, иначе он подставит CORP\alice, часть 16): PAM вызывает pam_sss, sssd делает AS-REQ с предварительной аутентификацией к контроллеру (часть 6), получает TGT и проверяет его ключом из keytab (krb5_validate, включённый в провайдере ad): просит по этому TGT билет для принципала машины из keytab и расшифровывает его своим ключом — так машина убеждается, что отвечал настоящий KDC, а не подставной. Затем pam_acct_mgmt спрашивает access_provider, и при первом входе pam_mkhomedir создаёт /home/alice@corp.example.com. TGT ложится в файл /tmp/krb5cc_<UID>_XXXXXX (суффикс случайный); в Fedora и RHEL по умолчанию кеш другой — KCM, Kerberos Credential Manager, где билеты хранит демон sssd-kcm. С этим билетом alice ходит на файловые ресурсы Windows по SMB и по SSH на другие машины домена без пароля — об этом следующая часть.

Кто может входить — три способа, от простого к гибкому.

realm permit — список пользователей или групп:

$ sudo realm permit -g linux-admins
$ sudo grep -E '^(access_provider|simple_allow_groups)' /etc/sssd/sssd.conf
access_provider = simple
simple_allow_groups = linux-admins
$ realm list | tail -3
  login-policy: allow-permitted-logins
  permitted-logins:
  permitted-groups: linux-admins

realm permit -g переключает access_provider на simple — и вместе с этим пропадают проверки отключённых учётных записей и прав из GPO, которые делал провайдер ad. realm permit alice bob разрешает конкретных пользователей, realm permit --all возвращает умолчание после вступления — login-policy: allow-realm-logins, вход всем пользователям домена.

LDAP-фильтр. Остаться на access_provider = ad и задать ad_access_filter; для вложенных групп — с правилом LDAP_MATCHING_RULE_IN_CHAIN из части 5, и тогда sssd требует полную форму с именем домена:

ad_access_filter = DOM:corp.example.com:(memberOf:1.2.840.113556.1.4.1941:=CN=linux-admins,OU=Groups,DC=corp,DC=example,DC=com)

Права входа из GPO с enforcing: «кто входит на Linux-серверы» решается в той же политике, что для Windows, — в «Allow log on through Remote Desktop Services» политики, привязанной к OU Linux-серверов, указывается доменная группа. sshd в списке ad_gpo_map_remote_interactive уже по умолчанию.

sudo — по группе каталога, файлом в /etc/sudoers.d/:

$ echo '%linux-admins ALL=(ALL) ALL' | sudo tee /etc/sudoers.d/linux-admins > /dev/null
$ sudo chmod 0440 /etc/sudoers.d/linux-admins
$ sudo visudo -cf /etc/sudoers.d/linux-admins
/etc/sudoers.d/linux-admins: parsed OK

С полными именами группа записывается %linux-admins@corp.example.com, с пробелом в имени — %domain\ admins. В Ubuntu 26.04 команда sudo — это sudo-rs, переписанный на Rust sudo с тем же форматом sudoers; группы sssd он понимает так же. Правила sudo можно хранить и в каталоге (схема sudoers для LDAP, sudo_provider = ad в sssd и sudoers: files sss в nsswitch.conf), и тогда они едут на все машины сами, но это требует расширения схемы, которое стоит делать осознанно (часть 5), и классического sudo: sudo-rs правила из LDAP не читает.


Доверенные домены

sssd с id_provider = ad сам находит остальные домены своего леса через глобальный каталог: пользователи eu.corp.example.com из части 21 видны как bob@eu.corp.example.com и входят, если разрешены, и каждому домену достаётся свой срез UID. Если доменов много, а нужны не все, ad_enabled_domains = corp.example.com ограничит список и ускорит старт.

Доверие между лесами (часть 22) sssd так не использует: документация провайдера прямо говорит, что распознаются только домены своего леса.

$ sudo sssctl domain-list
corp.example.com
$ id bob@partner.example.net
id: ‘bob@partner.example.net’: no such user

Выход

$ sudo realm leave -U alice.admin corp.example.com

-U подразумевает --remove: учётная запись компьютера удаляется или отключается в каталоге. Просто realm leave без учётных данных снимает настройку только локально — конфигурацию sssd и ключи, — а учётную запись придётся удалить с Windows-стороны.


Смотрим руками

Поиск пользователя, которого ещё нет в кеше, со стороны машины:

$ sudo timeout 6 tcpdump -i eth0 -n -q 'port 88 or port 389 or port 3268' &
$ id bob
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), snapshot length 262144 bytes
uid=1565001105(bob) gid=1565000513(domain users) groups=1565000513(domain users),1565001145(linux-admins)
09:47:08.343635 IP 192.168.1.60.46116 > 192.168.1.10.389: tcp 491
09:47:08.344618 IP 192.168.1.10.389 > 192.168.1.60.46116: tcp 1176
09:47:08.344642 IP 192.168.1.60.46116 > 192.168.1.10.389: tcp 0
09:47:08.376274 IP 192.168.1.60.46116 > 192.168.1.10.389: tcp 475
09:47:08.376971 IP 192.168.1.10.389 > 192.168.1.60.46116: tcp 1176
09:47:08.377886 IP 192.168.1.60.46116 > 192.168.1.10.389: tcp 159
09:47:08.378475 IP 192.168.1.10.389 > 192.168.1.60.46116: tcp 429
09:47:08.410932 IP 192.168.1.10.389 > 192.168.1.60.46116: tcp 429
09:47:08.410948 IP 192.168.1.60.46116 > 192.168.1.10.389: tcp 0
9 packets captured
9 packets received by filter
0 packets dropped by kernel

Kerberos здесь нет вовсе: sssd держит открытое соединение LDAP с контроллером (до 15 минут, ldap_connection_expire_timeout), привязанное когда-то билетом LNX1$, а TGT машины лежит в /var/lib/sss/db/ccache_CORP.EXAMPLE.COM. Три пары «запрос — ответ»; что в них, tcpdump -A не покажет — содержимое запечатано GSSAPI, ключом сеанса Kerberos (по документации провайдер ad ищет пользователя, а его группы читает одним атрибутом tokenGroups). Последний ответ пришёл дважды — повтор TCP. К глобальному каталогу (3268) sssd обращается за пользователями других доменов леса; в лесу из одного домена туда не ходит. На контроллере машина видна в журнале Security как 4768 (TGT для LNX1$) и 4769, где в поле имени службы стоит DC1$, — билет на LDAP контроллера, полученный, когда соединение открывалось.

Что sssd делает в DNS. Запись lnx1 может появиться раньше, чем машина вступила в домен: её создал администратор или DHCP-сервер, который регистрирует записи за клиентов (часть 8). Тогда у записи другой владелец, у LNX1$ нет на неё прав, и ежедневное обновление выглядит так:

$ sudo journalctl -u sssd --since '-1 day' --no-pager
Oct 30 09:34:16 lnx1.corp.example.com sssd[59860]: ; TSIG error with server: tsig verify failure
Oct 30 09:34:16 lnx1.corp.example.com sssd[59860]: ; TSIG error with server: tsig verify failure
Oct 30 09:34:16 lnx1.corp.example.com sssd[59870]: ; TSIG error with server: tsig verify failure
Oct 30 09:34:16 lnx1.corp.example.com sssd[59870]: ; TSIG error with server: tsig verify failure
Oct 30 09:34:16 lnx1.corp.example.com sssd[59875]: ; TSIG error with server: tsig verify failure
Oct 30 09:34:16 lnx1.corp.example.com sssd[59875]: update failed: REFUSED
$ sudo grep 'Dyndns update' /var/log/sssd/sssd_corp.example.com.log | tail -1
(2026-10-30  9:34:16): [be[corp.example.com]] [be_ptask_done] (0x0040): Task [Dyndns update]: failed with [1432158240]: Dynamic DNS update failed

Каждая строка с номером процесса — отдельный запуск nsupdate. TSIG (RFC 8945) — подпись сообщения DNS общим ключом, GSS-TSIG — тот же механизм с ключом, согласованным по Kerberos; tsig verify failure значит, что подпись ответа сервера nsupdate проверить не смог, а суть — в последней строке: сервер отказал (REFUSED). Отказ относится к записи, на которую у LNX1$ нет прав. В зоне, которая принимает только безопасные обновления, это и A, и PTR. Если прямая зона принимает и незащищённые, A sssd всё-таки перепишет — со своим TTL 3600 (в журнале аудита DNS на контроллере это пара событий 520 и 519 с адресом машины), а DHCP-сервер при продлении аренды вернёт свой; REFUSED тогда приходит от обратной зоны. Со стороны Windows:

PS C:\> Get-DnsServerResourceRecord -ComputerName dc1 -ZoneName corp.example.com -Name lnx1 | Select-Object HostName, RecordType, Timestamp, TimeToLive, @{n='Data'; e={$_.RecordData.IPv4Address}}

HostName RecordType Timestamp           TimeToLive Data
-------- ---------- ---------           ---------- ----
lnx1     A          30.10.2026 12:00:00 01:00:00   192.168.1.60

PS C:\> (Get-Acl 'AD:\DC=lnx1,DC=corp.example.com,CN=MicrosoftDNS,DC=ForestDnsZones,DC=corp,DC=example,DC=com').Owner
CORP\Domain Admins
PS C:\> Import-Module DhcpServer -SkipEditionCheck
PS C:\> Get-DhcpServerv4Lease -ComputerName dc1 -IPAddress 192.168.1.60 | Select-Object IPAddress, HostName, AddressState, DnsRR, DnsRegistration

IPAddress    HostName              AddressState      DnsRR   DnsRegistration
---------    --------              ------------      -----   ---------------
192.168.1.60 lnx1.corp.example.com ActiveReservation AandPTR Complete

Запись динамическая (есть Timestamp), принадлежит Domain Admins, а A и PTR для этого адреса регистрирует DHCP-сервер. Регистратор у записи должен быть один. Либо его оставляют DHCP-серверу, а в sssd.conf пишут dyndns_update = False; либо отдают sssd: DHCP-серверу запрещают регистрацию для резервирования (Set-DhcpServerv4DnsSetting -ComputerName dc1 -IPAddress 192.168.1.60 -DynamicUpdates Never), старую запись удаляют, и sssd при следующем обновлении создаёт её заново — уже с владельцем LNX1$.

Проверка отдельного пользователя через NSS и авторизацию PAM, не входя им:

$ sudo sssctl user-checks alice -s sshd -a acct
user: alice
action: acct
service: sshd

SSSD nss user lookup result:
 - user name: alice
 - user id: 1565001104
 - group id: 1565000513
 - gecos: Alice Smith
 - home directory: /home/alice@corp.example.com
 - shell: /bin/bash

SSSD InfoPipe user lookup result:
 - name: alice
 - uidNumber: 1565001104
 - gidNumber: 1565000513
 - gecos: Alice Smith
 - homeDirectory: not set
 - loginShell: not set

testing pam_acct_mgmt

pam_acct_mgmt: Success

PAM Environment:
 - no env -

NSS отдаёт домашний каталог и оболочку из умолчаний sssd.conf, InfoPipe (интерфейс sssd на D-Bus) — то, что лежит в каталоге, а там их нет. pam_acct_mgmt — авторизация: access_provider, права из GPO, отключённая учётная запись. С -a auth команда спросит пароль и пройдёт pam_authenticate, то есть получит TGT. Если авторизация возвращает Permission denied, причина — в /var/log/sssd/sssd_corp.example.com.log при debug_level = 6 в секции домена. Кеш, когда в каталоге что-то поменяли, а машина этого не видит: sudo sss_cache -E помечает устаревшими все записи, -u alice — одного пользователя; без этого запись живёт полтора часа (entry_cache_timeout = 5400). Новые группы у уже вошедшего пользователя появляются только в новом сеансе, как и на Windows.

И состояние членства одной командой:

$ realm list
corp.example.com
  type: kerberos
  realm-name: CORP.EXAMPLE.COM
  domain-name: corp.example.com
  configured: kerberos-member
  server-software: active-directory
  client-software: sssd
  required-package: sssd-tools
  required-package: sssd
  required-package: libnss-sss
  required-package: libpam-sss
  required-package: adcli
  required-package: samba-common-bin
  login-formats: %U
  login-policy: allow-permitted-logins
  permitted-logins:
  permitted-groups: linux-admins

P.S. RC4 на машинах с Ubuntu 24.04

Машина, введённая в домен с Ubuntu 24.04, остаётся с RC4: msDS-SupportedEncryptionTypes = 28 и ключи arcfour-hmac в keytab. Закрывать это срочно незачем. Ключ RC4 учётной записи компьютера — хеш случайного пароля из 120 символов, подобрать его нельзя, а билеты к машине KDC и так выдаёт с AES: и Windows-клиенты, и sssd предлагают его первым. Но Microsoft выводит RC4 из Kerberos: после обновлений 2026 года (KB5073381) контроллеры считают, что учётная запись без этого атрибута поддерживает только AES, а явно записанное значение соблюдают — с 28 RC4 у машины останется разрешённым, пока его не убрать руками. Чтобы машины 24.04 не отличались от 26.04, хватит двух команд, и обе обратимы.

Сначала убедиться, что RC4 к машине никто не просит: в событиях 4769 для её учётной записи тип шифрования билета должен быть 0x12 (AES256) или 0x11 (AES128), а не 0x17 (RC4). Журнал Security у каждого контроллера свой:

PS C:\> $x = "*[System[EventID=4769] and EventData[Data[@Name='ServiceName']='LNX1`$']]"
PS C:\> Invoke-Command -ComputerName dc1, dc2 -ArgumentList $x -ScriptBlock { param($x) Get-WinEvent -LogName Security -FilterXPath $x -ErrorAction SilentlyContinue | ForEach-Object { ([xml]$_.ToXml()).Event.EventData.Data | Where-Object Name -eq 'TicketEncryptionType' | ForEach-Object '#text' } | Group-Object | Select-Object Count, Name } | Format-Table PSComputerName, Count, Name

PSComputerName       Count Name
--------------       ----- ----
dc1.corp.example.com     1 0x12
dc2.corp.example.com     1 0x12

Затем оставить в атрибуте только AES — на том контроллере, через который потом пойдёт adcli, чтобы он прочитал новое значение, не дожидаясь репликации:

PS C:\> Set-ADComputer lnx1 -Server dc1 -KerberosEncryptionType AES128,AES256

И сменить пароль машины сразу, не дожидаясь, пока через 30 дней это сделает sssd:

$ sudo adcli update --verbose --domain-controller=dc1.corp.example.com --computer-password-lifetime=0 2>&1 | grep -E 'Changed computer password|kvno incremented'
 * Changed computer password
 * kvno incremented to 12
$ sudo klist -k -e
Keytab name: FILE:/etc/krb5.keytab
KVNO Principal
---- --------------------------------------------------------------------------
  11 LNX1$@CORP.EXAMPLE.COM (DEPRECATED:arcfour-hmac)
  11 LNX1$@CORP.EXAMPLE.COM (aes128-cts-hmac-sha1-96)
  11 LNX1$@CORP.EXAMPLE.COM (aes256-cts-hmac-sha1-96)
  11 host/LNX1@CORP.EXAMPLE.COM (DEPRECATED:arcfour-hmac)
  11 host/LNX1@CORP.EXAMPLE.COM (aes128-cts-hmac-sha1-96)
  11 host/LNX1@CORP.EXAMPLE.COM (aes256-cts-hmac-sha1-96)
  11 host/lnx1.corp.example.com@CORP.EXAMPLE.COM (DEPRECATED:arcfour-hmac)
  11 host/lnx1.corp.example.com@CORP.EXAMPLE.COM (aes128-cts-hmac-sha1-96)
  11 host/lnx1.corp.example.com@CORP.EXAMPLE.COM (aes256-cts-hmac-sha1-96)
  11 RestrictedKrbHost/LNX1@CORP.EXAMPLE.COM (DEPRECATED:arcfour-hmac)
  11 RestrictedKrbHost/LNX1@CORP.EXAMPLE.COM (aes128-cts-hmac-sha1-96)
  11 RestrictedKrbHost/LNX1@CORP.EXAMPLE.COM (aes256-cts-hmac-sha1-96)
  11 RestrictedKrbHost/lnx1.corp.example.com@CORP.EXAMPLE.COM (DEPRECATED:arcfour-hmac)
  11 RestrictedKrbHost/lnx1.corp.example.com@CORP.EXAMPLE.COM (aes128-cts-hmac-sha1-96)
  11 RestrictedKrbHost/lnx1.corp.example.com@CORP.EXAMPLE.COM (aes256-cts-hmac-sha1-96)
  12 LNX1$@CORP.EXAMPLE.COM (aes128-cts-hmac-sha1-96)
  12 LNX1$@CORP.EXAMPLE.COM (aes256-cts-hmac-sha1-96)
  12 host/LNX1@CORP.EXAMPLE.COM (aes128-cts-hmac-sha1-96)
  12 host/LNX1@CORP.EXAMPLE.COM (aes256-cts-hmac-sha1-96)
  12 host/lnx1.corp.example.com@CORP.EXAMPLE.COM (aes128-cts-hmac-sha1-96)
  12 host/lnx1.corp.example.com@CORP.EXAMPLE.COM (aes256-cts-hmac-sha1-96)
  12 RestrictedKrbHost/LNX1@CORP.EXAMPLE.COM (aes128-cts-hmac-sha1-96)
  12 RestrictedKrbHost/LNX1@CORP.EXAMPLE.COM (aes256-cts-hmac-sha1-96)
  12 RestrictedKrbHost/lnx1.corp.example.com@CORP.EXAMPLE.COM (aes128-cts-hmac-sha1-96)
  12 RestrictedKrbHost/lnx1.corp.example.com@CORP.EXAMPLE.COM (aes256-cts-hmac-sha1-96)

krb5.conf править не нужно: при обновлении adcli берёт текущее значение msDS-SupportedEncryptionTypes и создаёт ключи по нему, так что новое поколение 12 — только AES, а атрибут так и остаётся 24:

PS C:\> Get-ADComputer lnx1 -Properties msDS-SupportedEncryptionTypes, KerberosEncryptionType, msDS-KeyVersionNumber | Format-List Name, msDS-SupportedEncryptionTypes, KerberosEncryptionType, msDS-KeyVersionNumber

Name                          : LNX1
msDS-SupportedEncryptionTypes : 24
KerberosEncryptionType        : {AES128, AES256}
msDS-KeyVersionNumber         : 12

Поколение 11 с ключом RC4 adcli оставил нарочно — оно нужно, пока новый пароль расходится по контроллерам, — и при следующей смене пароля оно исчезнет. В эти минуты adcli testjoin через контроллер, который новый пароль ещё не получил, отвечает Couldn't authenticate as machine account: LNX1: Preauthentication failed; после репликации (между сайтами — до её интервала) проходит. Вернуть RC4 — -KerberosEncryptionType RC4,AES128,AES256: ключи AES в keytab остаются при любом значении, так что вход не теряется.


Источники

Вступление в домен:

  • realm(8) — discover, join с --computer-ou, --os-name, --automatic-id-mapping, leave и --remove, permit;
  • realmd.conf(5) — default-home = /home/%U@%D, fully-qualified-names, automatic-id-mapping, manage-system;
  • adcli(8) — join, update, info, полное имя через gethostname() и getaddrinfo(), флаг DONT_EXPIRE_PASSWORD, сохранение предыдущего ключа, текущее значение msDS-supportedEncryptionTypes при обновлении, --computer-password-lifetime, --os-name;
  • Ubuntu Server: Join a domain with SSSD — пакеты, realm join, mkhomedir.

SSSD:

  • sssd-ad(5) — провайдер ad: только домены своего леса, ad_access_filter и DOM:, ad_gpo_access_control и ad_gpo_map_*, встроенные группы, dyndns_*, ad_maximum_machine_account_password_age, алгоритм ID mapping, умолчания krb5_validate и ldap_use_tokengroups;
  • sssd.conf(5) — cache_credentials, offline_credentials_expiration, entry_cache_timeout, debug_level;
  • sssd-krb5(5) — krb5_validate, krb5_ccname_template, krb5_store_password_if_offline;
  • sssd-ldap(5) — ldap_connection_expire_timeout, ldap_use_tokengroups;
  • sssd-simple(5) — simple_allow_groups и simple_allow_users;
  • sssctl(8) — domain-status, domain-list, user-checks, config-check;
  • sss_cache(8) — пометка записей кеша устаревшими;
  • pam_sss(8) — модуль PAM и use_first_pass;
  • sssd_krb5_locator_plugin(8) — как libkrb5 узнаёт KDC от sssd;
  • sssd-sudo(5) — правила sudo из каталога;
  • winbindd(8) — альтернатива sssd в составе Samba.

Kerberos:

  • krb5.conf — includedir, default_realm, udp_preference_limit, [domain_realm], правило auth_to_local по умолчанию, permitted_enctypes;
  • keytab и klist — файл ключей и его просмотр;
  • RFC 4120 — протокол Kerberos;
  • RFC 3244 — смена и установка пароля через Kerberos;
  • RFC 4422 и RFC 4178 — SASL и SPNEGO;
  • [MS-KILE] msDS-SupportedEncryptionTypes — биты типов шифрования;
  • KB5073381 — RC4 после обновлений 2026 года: только AES для учётных записей без msDS-SupportedEncryptionTypes, явно заданное значение соблюдается;
  • 4769(S, F): A Kerberos service ticket was requested — поле Ticket Encryption Type и его значения.

Каталог:

DNS и DHCP:

Права входа:

Система:


Машина в домене, пользователи входят по паролю, и у каждого после входа есть TGT. Этот TGT умеет больше, чем лежать в кеше: с ним можно войти на следующую машину без пароля, открыть веб-приложение без формы входа и дать службе на этой машине ходить в каталог от своего имени.

Дальше — часть 30: Kerberos как единый вход. GSSAPI в SSH с обеих сторон — Linux и Windows — и делегирование билета; SPN для служб на Linux и keytab для них; аутентификация Negotiate в веб-приложениях за Apache и nginx; делегирование на практике с Linux-стороны; и куда смотреть, когда вместо единого входа снова спрашивают пароль.

Оставить комментарий могут только зарегистрированные пользователи.

Войдите на сайт или зарегистрируйтесь, чтобы оставить комментарий.