Официальная возможность получить лицензионный софт бесплатно.
Giveaway of the Day
Это не реклама!

Щелкните для получения прогноза по Биробиджану


четверг, 10 ноября 2016 г.

bash: Рекурсивно разворачиваем доменную группу в список пользователей

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

Синтаксис виндовой dsget и её родственных утилит вызывает у меня стойкое отвращение обилием лишних параметров и общей запутанностью. Но раз уж прокси-сервер у меня работает под убунтой, то почему бы не задействовать мощный язык bash-a и возможности Samba?

Что у нас есть из инструментов? В первую и главную очередь - сильномогучая команда net. Во вторую - очень полезная команда wbinfo. Надо скомпоновать результаты их работы, чтобы на выходе получить список пользователей группы и всех входящих в неё подгрупп.

Далее по тексту подразумевается, что всё выполняется на компьютере с установленной Samba, введённом в домен. Разделитель домена и имени объекта - обратный слэш "\".
Все команды выполняются от имени супер-пользователя.
Начнём с очевидного: получить список пользователей, входящих в группу:

# getent group имя_группы

Ой! А почему для доменной группы оно выдало только ее название и GID? Где члены группы? Увы, getent не очень хорошо работает с доменными группами, а значит бесполезна для нас. Попробуем альтернативу.

# net group members list имя_группы

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

# net rpc group members имя_группы -P -S FQDN_или_IP_контроллера_домена

RPC - это протокол, по которому будем общаться с DC.
-P (заглавная!) - будем притворяться компьютером, то есть, использовать для авторизации на контроллере учетные данные компа, с которого работаем. Соответственно, полномочий меньше (но для наших целей достаточно), а имя и пароль вводить не требуется.
- S - иногда самба не может нормально понять, с каким DC работать, поэтому лучше явно указать ближайший. Контроллер может быть задан своим полным именем, например dc2016.contoso.com или IP-адресом. (я не проверял, можно ли использовать IPv6 за неимением оного протокола в моей сети).
# net rpc group members inet053 -P -S dc.contoso.com
CONTOSO\users053


Опа! users053 - это группа, в которую включены все работники службы "053", например, чтобы их скопом включать в другие группы. Это хорошо, что я вижу, что она включена полностью в группу управления доступом inet053, но легче от этого не становится, я ж не могу помнить всех работников этой службы.
# net rpc group members users053 -P -S dc.contoso.com
CONTOSO\сергей-а
CONTOSO\сергей-ф
CONTOSO\евгений-к
CONTOSO\ира-г
CONTOSO\оля-р
CONTOSO\remotes

Уже легче. Но видно, что в группу users053 входит еще группа remotes, то есть глубина вложенности не определена и заранее неизвестна. Теперь надо заходить в remotes, смотреть ее список пользователей и т.д. В общем, классическая задача рекурсивного обхода дерева. Абсолютное большинство языков программирования общего назначения легко справятся, а что насчет bash-a?
Ответ выглядит простым: в баше есть функции и есть рекурсия. Но об этом потом.
Очевидный алгоритм:
1. получить список членов группы
2. если есть обычные пользователи, то сразу вывести их список
3. для каждой вложенной группы повторить, начиная с шага 1.
Проблема за малым: в выводе net group members совершенно невозможно отличить автоматически группы от пользователей и других объектов. И вот тут обратимся за помощью к wbinfo:
# wbinfo -n имя_объекта
вернет нам SID этого объекта с краткой расшифровкой:
# wbinfo -n сергей-а
S-1-5-21-5*******6-4********3-3********3-2***8 SID_USER (1)

# wbinfo -n users053
S-1-5-21-5*******6-4********3-3********3-1***6 SID_DOM_GROUP (2)


Мне лень расшифровывать SID-ы самому, поэтому поверю в способности wbinfo.
(псевдокод)
if right(wbinfo-result) = "SID_USER (1)"
  then ObjectType="U"
  else  if right(wbinfo-result) = "SID_DOM_GROUP (2)"
    then ObjectType="G"
    else ObjectType="-"
  endif
endif

То есть, надо пройтись по списку членов группы и проставить каждому "расовую принадлежность". В зависимости от типа объекта, либо снова получать список его членов, либо просто вывести его, либо проигнорировать, если это не группа и не пользователь.
На этом самбовская часть работы закончена, осталось лишь обернуть ее в программный код, который будет управлять процессом. Вероятно, потребуется создать функцию получения списка членов группы, которая при необходимости будет вызывать саму себя для раскрытия подгрупп в текущей группе.
Итак, готовый скрипт. Для красоты пользователи и вложенные группы выводятся со сдвигом вправо в зависимости от глубины вложенности. Но можно этого не делать. Весь вывод выполняется одной командой, настроить формат которой можете по своему усмотрению.
#!/bin/bash

# Глобальные переменные
cur_level=0
users_total=0
cur_group=1
cur_user=0
# Имя контроллера домена, используется в функции get_memb

DC_name=dc2016.contoso.com

# Аналог ДОСовской команды PAUSE

# Использовался при отладке этого скрипта, сейчас не нужен, но оставил на всякий случай
pause() {
read -sn1 -p "Press any key to continue..."
}

# Получаем список членов группы, имя которой передано в параметре $1
# использование:
# переменная=$(get_memb ИМЯ_ГРУППЫ)
# Не забываем задать правильное имя контроллера в переменной DC_name до первого вызова этой функции
get_memb() {
local loc_1

    # Убираем, если есть, префикс домена с его бэкслешами
    # (замените слово domain в выражении sed-a на имя своего домена
    loc_1=$(echo "$1"|sed -e 's/^[dD][oO][mM][aA][iI][nN]\\*//')
    # результаты сортируем просто для удобства
    net rpc group members "$loc_1" -P -S $DC_name|sort -f
}

# Возвращает тип объекта: "U" - пользователь, "G" - группа, "-" - х.з. что
# использование:
# переменная=$(ana_type ИМЯ_ОБЪЕКТА)
# где ИМЯ_ОБЪЕКТА в стандартной доменной нотации вида домен\объект, как его возвращает get_memb()
ana_type() {
local otype e
    # Результат обращения к wbinfo сохраним в переменной, чтобы не дёргать ее
дважды
   
otype=$(wbinfo -n "$1")
    # Если есть более кошерный способ искать подстроку в переменной, рад буду узнать
    echo $otype|grep -q "SID_USER (1)"
    # На всякий случай сохраняем код возврата из grep в переменную. Может работать и без такого сохранения.
    e=$?
    if [ $e -eq 0 ]
    
then   
   
    echo U
    else
   
    echo $otype|grep -q "SID_DOM_GROUP (2)"
   
    e=$?
   
    if [ $e -eq 0 ]
   
    then
       
    echo G
   
    else
       
    echo -
   
    fi
    fi
}

# Печатает строку из $1 повторений символа или строки $2 (почему-то не работает с пробелами)
# Используется для отступа, показывающего глубину вложенности. Не обязательно. Совсем.
repchar() {
local a
if [ "0" -ne "$1" ]; then for a in $(seq 1 $1); do echo -n $2; done; fi
}

# Основной цикл
# $1 - имя группы для обработки (с префиксом домена или без оного)
# переменные явно объявлены локально, чтобы избежать проблем при рекурсии
parse_group() {
local a b itype members nmembers

# Получение списка членов группы - процесс небыстрый, поэтому его результаты сохраним в переменной: они нужны как минимум дважды
    members=$(get_memb "$1")
    nmembers=$(echo $members|wc -w)

    # В группе не найдено участников? Идём мимо нее
    if [ "0" -eq "$nmembers" ]; then return; fi    # Есть участники? Увеличим счетчик глубины вложенности
    cur_level=$[$cur_level+1]
    for a in $members; do
    b=$[$b+1]
    itype=$(ana_type "$a")
    echo $(repchar $cur_level "_") $cur_level\:$cur_group\:$b\:$itype\:$a

    # Это пользователь? Увеличим счетчик и идем дальше
    if [ "$itype" == "U" ]; then users_total=$[$users_total+1]; fi 
    # Это группа? Вызываем себя рекурсивно для её разбора
    if [ "$itype" == "G" ]; then parse_group $a; fi
    done
    cur_group=$[$cur_group+1]
 
    # Обработали, может подымемся на предыдущий уровень вложенности?
    cur_level=$[$cur_level-1]
}



# два варианта запуска, например.
# учтите, что разворачивание группы "Пользователи домена" может быть очень долгим.
parse_group "domain users"
parse_group "$1"
echo Users found:$users_total

Комментариев нет:

Отправить комментарий

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