Показаны сообщения с ярлыком active directory. Показать все сообщения
Показаны сообщения с ярлыком active directory. Показать все сообщения

воскресенье, 19 июля 2020 г.

Сертификат удостоверяющего центра Active Directory для веб-серверов

На работе в локальной сети имеется ряд веб-серверов, для которых понадобилось сделать поддержку протокола HTTPS. Поскольку веб-серверы находятся в локальной сети, то публичные удостоверяющие центры сертификаты для них выдать не могут. Можно было сгенерировать самоподписанные сертификаты, но тогда понадобилось бы вносить каждый сертификат в список доверенных в каждом браузере. Но есть выход получше. Т.к. компьютеры под управлением Windows в локальной сети объединены в домен Active Directory, они все должны доверять удостоверяющему центру Active Directory. Можно сгенерировать сертификаты для веб-серверов, подписать их в удостоверяющем центре Active Directory и тогда все браузеры, использующие системное хранилище сертификатов, на таких компьютерах будут автоматически доверять этим сертификатам.

Насколько я знаю, из самой популярной тройки браузеров только Firefox в конфигурации не использует системное хранилище сертификатов. Если учитывать, что большинство компьютеров в локальных сетях предприятий обычно работают под управлением Windows и включены в домен Active Directory, а большая часть пользователей используют браузеры Chrome и Internet Explorer, то таким образом можно внедрить HTTPS на веб-серверах в локальной сети максимально гладко. В случае с Firefox можно либо включить использование системного хранилища сертификатов Windows в настройках браузера, либо вручную импортировать в браузер корневой сертификат удостоверяющего центра Active Directory. Он всего один и срок его действия больше, чем у сертификатов веб-серверов.

По возможности лучше генерировать для каждого веб-сервера индивидуальный сертификат и тогда при взломе одного веб-сервера трафик остальных останется защищённым, т.к. злоумышленник не сможет использовать приватный ключ со взломанного сервера для расшифровки перехваченного трафика других веб-серверов или организации атаки посредника. Я же для экономии времени предпочёл сегенерировать один сертификат сразу для всех веб-серверов, находящихся в моей зоне ответственности. Чтобы при необходимости продлить сертификат мне не пришлось бы вспоминать значения полей из запроса на сертификат и не пропустить по невнимательности ни одного из веб-серверов, я подготовил файл конфигурации cert-web.ini такого вида:
[req]
distinguished_name = req_distinguished_name
req_extensions = v3_req

[req_distinguished_name]
countryName = RU
countryName_default = RU
stateOrProvinceName = Bashkortostan Republic
stateOrProvinceName_default = Bashkortostan Republic
localityName = Ufa
localityName_default = Ufa
organizationalUnitName = My Department
organizationalUnitName_default = My Department
commonName = My Company
commonName_default = My Company
commonName_max  = 64

[ v3_req ]
# Extensions to add to a certificate request
basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
subjectAltName = @alt_names

[alt_names]
DNS.1 = server1.domain1.tld
DNS.2 = server2.domain1.tld
DNS.3 = server3.domain1.tld
DNS.4 = server4.domain2.tld
DNS.5 = server5.domain2.tld
DNS.6 = server6.domain3.tld
В конфигурации упоминаются:
  • RU - код страны,
  • Bashkortostan Republic - административная единица внутри страны,
  • Ufa - название населённого пункта,
  • My Company - название компании,
  • My Department - название подразделения компании,
  • serverX.domainY.tld - доменные имена веб-серверов в локальной сети.
Генерируем приватный ключ:
$ openssl genrsa -out cert-web.key 2048
Генерируем запрос на сертификат в соответствии с файлом конфигурации:
$ openssl req -config cert-web.ini -new -key cert-web.key -out cert-web.csr
Запрос на сертификат из файла cert-web.csr я передал администратору домена Active Directory, который подписал его в удостоверяющем центре и вернул мне подписанный сертификат cert-web.cer в формате DER.

Полученный сертификат надо преборазовать из формата DER в формат PEM:
$ openssl x509 -inform der -in cert-web.cer -out cert-web.crt
Осталось соединить приватный ключ и сертификат в формате PEM для использования получившегося файла веб-сервером:
$ cat cert-web.key cert-web.crt > cert-web.pem
Остаётся положить получившийся один файл на веб-серверы и задействовать их использование в конфигурациях веб-серверов. Если использовать систему автоматизированного управления конфигурациями, то эта задача не займёт много времени. После освоения Ansible я стал раскладывать сертификаты на веб-серверы именно с её помощью.

Для того, чтобы получить корневой сертификат удостоверяющего центра Active Directory, нужно зайти веб-браузером на сервер с удостоверяющим центром, где можно будет найти и скачать корневой сертификат. В моём случае корневой сертификат удостоверяющего центра был доступен по ссылке вида: https://domain1.tld/certsrv/certnew.cer?ReqID=CACert&Renewal=2&Mode=inst&Enc=b64

Т.к. на веб-серверах был доступен API, который использовался на других серверах, то корневой сертификат понадобилось добавить так же и на эти серверы. В случае с Debian это можно сделать способом, описанным ниже.

Сначала устанавливаем стандартные сертификаты удостоверяющих центров, если они ещё не были установлены:
# apt-get install ca-certificates
Кладём корневой сертификат нашего удостоверяющего центра в каталог /usr/local/share/ca-certificates/, предназначенный специально для дополнительных сертификатов удостоверяющих центров.

Обновляем список корневых сертификатов, которым должна доверять библиотека openssl:
# update-ca-certificates
После этого все установленные в системе программы должны начать доверять сгенерированным нами сертификатам веб-серверов. Если этого не случилось и какая-то программа или модуль не начали доверять новым сертификатам, изучите документацию. Например, для того, чтобы модуль urllib2 для Python начал доверять сертификатам, мне понадобилось передавать библиотеке urllib2 дополнительные настройки, описанные в заметке: Проверка действительности SSL-сертификата в urllib2

понедельник, 19 марта 2012 г.

Запросы и обновления Active Directory

Эта статься взята из журнала Windows IT Pro/RE за июнь 2006 года. Текст статьи можно найти по ссылке http://www.osp.ru/win2000/2006/04/2578905/, однако там отсутствуют листинги программ - возможно они потерялись при изменении структуры сайта. Найти копии этой статьи с листингами в интернете мне не удалось, поэтому нашёл сам журнал (на торрентах) и набрал листинги сам. Если правообладатели выложат на сайте листинги, готов убрать статью со своего блога.

Чем меня привлекла эта статья? Неделю назад я написал на Perl небольшой скрипт с использованием Net::LDAP. Документация на английском очень хороша, но мне приятнее обращаться к документации как к справочнику, а усваивать что-то новое проще всё-таки на русском. Хорошим учебником по этой теме может послужить книга Дэвида Бланк-Эдельмана "Perl для системного администрирования", в которой есть глава, посвящённая использованию модулей Net::LDAP и Mozilla::LDAP. Недостаток главы из этой книги заключается в том, что там оба модуля рассматриваются параллельно и читать из-за этого её не очень удобно. В этой статье сжато рассмотрены все основные операции по работе с каталогом LDAP применительно только к модулю Net::LDAP.

Хочу ещё отметить, что я совсем не ожидал увидеть такую статью в журнале, целиком посвящённом использованию Windows. Perl, как мне кажется, чужд системным администраторам Windows.

Запросы и обновления Active Directory

Использование модулей Perl Net::LDAP

Для автоматизации и организации программного доступа к Active Directory (AD) обычно выбирают интерфейс API, называемый Microsoft Active Directory Service Interfaces, ADSI. ADSI достаточно прост в использовании и имеет понятный интерфейс, позволяющий легко управлять объектами в AD. Благодаря комбинации ADSI и ADO для осуществления запросов можно выполнять практически любые действия, автоматизирующие использование AD.

Поскольку ADSI построен на модели COM, разработанной и применяемой только Microsoft, воспользоваться ADSI на отличных от Windows платформах не удастся. Использование ADSI для разработки сценариев или приложений для других операционных систем, а также разработка независимых от платформы сценариев или приложений затруднены. К счастью, существует альтернатива: можно использовать Lightweight Directory Access Protocol (LDAP) и его API.

Записи журналов Microsoft для поддерживаемых стандартов не всегда хороши, но с появлением AD специалисты Microsoft внесли изменения, которые заметно улучшили положение. AD поддерживает не только LDAP, но и другие стандарты, такие как DNS, Simple Network Time Protocol (SNTP), Secure Sockets Layer (SSL), Transport Layer Security (TLS) и Kerberos. Одна из причин взаимодействия AD с LDAP состоит в том, что LDAP делает AD более независимой от платформы с точки зрения клиента. Поскольку LDAP является стандартом, вы не ограничены Windows-клиентами и Windows-платформой. LDAP существует уже достаточно давно и имеет клиентов практически для всех платформ. Это означает, что вы можете писать и использовать сценарии для клиентов, поддерживающих LDAP и осуществляющих запросы и обновления AD с выбранной платформы. Такая возможность будет полезна для администраторов, которым необходимо разрабатывать сценарии и приложения для отличных от Windows платформ, а также для тех, кто пишет кроссплатформенные приложения и сценарии, и пользующие AD.

Многие думают, что LDAP — это всего-навсего протокол. Но, в отличие от большинства стандартов для протоколов, LDAP имеет определенный стандартом IETF) RFC программный интерфейс API. Он описан в RFC 1823 (http://www.ietf.org/rfc/rfc1823.txt) и обычно именуется C-style LDAP API. API содержит базовый набор функций для осуществления запросов и обновления основанного на LDAP каталога. Специалисты Microsoft создали соответствующий пакет разработчика, SDK, который используется для работы с LDAP API.

C-style LDAP API, являющийся практически стандартным для использования с LDAP, поначалу кажется удобным. Его недостаток заключается в том, что он не является объектно-ориентированным и недостаточно хорошо взаимодействует с некоторыми языками, такими как Java. Поэтому Sun Microsystems разработала собственный LDAP API, известный как Java Naming and Directory Interface, JNDI (см. http://java.sun.com/products/jndi). JNDI обладает расширенными возможностями по сравнению с LDAP API. JNDI является также интерфейсом к DNS, по идеологии похожим на ADSI, и может использоваться в качестве общего интерфейса к службе каталога.

В сообществе разработчиков Perl были созданы наборы Perl-модулей для LDAP, в основе которых лежит Netscape LDAP SDK, более известный как PerLDAP. В имени модулей PerLDAP имеется префикс Mozilla::LDAP. К сожалению, такие модули Perl требуют для использования дополнительной установки Netscape LDAP SDK. Другая группа разработчиков создала основанную на чистом Perl реализацию таких же модулей Perl, известных как perl-ldap. По именам они не пересекаются с модулями Netscape PerLDAP. Модули perl-ldap именуются префиксом Net::LDAP. Большое преимущество модулей Net::LDAP заключается в том, что вы можете установить модули Net::LDAP практически на любую систему, поддерживающую Perl. В результате появляется возможность написания основанных на LDAP клиентов без использования каких-либо внешних SDK или дополнительного программного обеспечения. Модули Net::LDAP используют большинство имен функций C-style API, но в отличие от них являются объектно-ориентированными и более простыми в применении.

В этой статье я объясню, как установить и использовать модули Net::LDAP для осуществления запросов к AD. Поскольку статья адресована пользователям, имеющим опыт работы с Perl, я не буду описывать процедуру установки. Мы обсудим использование модулей Net::LDAP для создания и обновления объектов в AD.

Установка Net::LDAP

У опытных пользователей Perl установка модулей не вызывает затруднений. При использовании для установки Net:: LDAP среды Comprehensive Perl Archive Network (CPAN) можно выполнить следующую команду:
> perl -MCPAN -e shell
cpan> install Net::LDAP
Если вы никогда не использовали среду CPAN, рекомендую изучить ее возможности как можно тщательнее, поскольку это значительно упростит процедуру установки и обновления модулей. Модуль CPAN.pm поставляется со многими дистрибутивами Perl. Когда пользователь запускает этот модуль в первый раз, программа установки проводит его через установку среды CPAN. Инструкции о том, как установить модули CPAN на различные платформы, можно найти на сайтах, посвященных CPAN (например, http://www.cpan.org/modules/INSTALL.html).

Ссылки на последнюю версию библиотеки perl-ldap вместе с документацией есть на домашней странице perl-ldap (http://perl-ldap.sourceforge.net). Версия кода perl-ldap, использованного в этой статье, perl-ldap-0.26.

Начало работы с Net::LDAP

Начнем с простого примера использования модулей Net::LDAP. Листинг 1 содержит код, возвращающий основную информацию из каталога. В этой программе я задействовал процедуру new() для создания нового соединения с контроллером домена (DC), имеющим имя dc1.

Листинг 1. Код, показывающий информацию из RootDSE
# Программа, показывающая информацию из RootDSE
use strict;
use Net::LDAP;
my $ldap = Net::LDAP->new('dc1') or die $@;
my $rootdse = $ldap->root_dse(attrs => ['defaultNamingContext']);
print $rootdse->get_value('defaultNamingContext');
После этого я вызвал процедуру root_dse для получения нужных атрибутов из Root DSE, который является хранилищем (репозитарием) информации о контроллере домена DC. В этом примере мне нужно получить атрибуты, определяющие контекст именования домена, поэтому я установил значение параметра attrs в defaultNamingContext. Чтобы получить все атрибуты в Root DSE, необходимо установить значение параметра attrs в (*). Затем я использовал процедуру get_value() для получения значений нужных атрибутов. Например, значение, возвращаемое для домена mycorp.com будет dc=mycorp, dc=com.

Заметим, что листинг 1 не содержит какого-либо кода для аутентификации. Root DSE доступен по анонимному доступу, чтобы приложения имели некую стартовую точку, определив ее из основной информации о каталоге. Для выполнения более сложных запросов понадобится включить в приложение аутентификационный код, подобный показанному в листинге 2. В этом коде я вызывал процедуру bind() и задавал требуемое полное имя distinguished name (DN) и пароль для учетной записи, от имени которой хотел регистрироваться. Метод bind() возвращает объект — сообщение Net::LDAP::Message, который я использую для определения того, были ли случаи ошибок во время аутентификации. Если ошибки были, метод code() возвращает код ошибки, а метод error() возвращает текстовое сообщение о ней. Для окончания сессии авторизации я использую процедуру unbind().

Листинг 2. Код аутентификации
# Код аутентификации
use strict;
use Net::LDAP;
my $dc = 'dc1';
my $user = 'cn=administrator, cn=users, dc=mycorp, dc=com';
my $passwd = 'Adminpasswd';
my $ldap = Net::LDAP->new($dc) or die $@;
my $rc = $ldap->bind($user, password => $password);
die $rc->error if $rc->code;
$ldap->unbind;

Запросы к AD

Для тех, кому хорошо известны параметры, использующиеся для поиска в LDAP, предпочтительным будет поиск при помощи Net::LDAP. Для выполнения поиска в LDAP обычно задаются три параметра; начальное полное имя DN, границы (scope) и фильтр. Параметр base DN задает точку, с которой начинается поиск. Параметр scope (границы) определяет границы (диапазон) поиска. Можно использовать одно из следующих значений:
  • base - соответствует только объекту, который задан в начальном DN,
  • onelevel или one - соответствует объектам, расположенным на один уровень ниже начального DN (т. е. прямые потомки родителя), исключая начальный DN,
  • subtree или sub - соответствует любым объектам, расположенным ниже начального DN, исключая начальный DN.
Параметр filter является префиксом строки, определяющей критерии, которым должны соответствовать объекты. Синтаксис фильтра определен в RFC 2254 (http://www.ietf.org/rfc/rfc2254.txt). В таблице 1 показано несколько простых фильтров поиска.

Таблица 1. Примеры фильтров поиска
Фильтр поискаКритерий поиска
(&(objectclass=user)(objectcategory=Person))Все пользовательсякие объекты в AD
(&(objectclass=user)(objectcategory=Precon)(sn=Allen)) Все объекты типа пользователь, имеющие фамилию Allen
(&(objectclass=computer)(objectcategory=Computer)(name=w2k*))Все объекты типа компьютер, начинающиеся с w2k
(&(objectclass=contact)(objectcategory=Person)(!description=*)))Все объекты контакты, не имеющие заполненного атрибута Description
В листинге 3 показан код программы, выполняющей запросы к AD для поиска всех пользователей, фамилия которых Allen. Заметьте, что переменная $user расположена в программном коде, выделенном в блоке A. Вместо задания полного имени пользователя user DN, я задаю основное имя пользователя user principal name (UPN), которое является идентификатором в стиле электронной почты и которое пользователь вводит при регистрации. Соответствие пользовательских имен UPN адресам электронной почты — достаточно распространенная практика. Если DNS-имя леса не соответствует DNS-суффиксу, используемому в e-mail-адресах, можно создать дополнительный суффикс UPN, выполнив шаги, описанные в статье Microsoft «HOW TO: Add UPN Suffixes to a Forest» (http://support.microsoft.com/?kbid=243629). Если вы используете UPN, вам нет необходимости задавать в программе полное DN-имя пользователя.

Листинг 3. Программа, выполняющая запросы к AD
# Программа, выполняющая запросы к AD
use strict;
use Net::LDAP;
# BEGIN CALLOUT A
# BEGIN COMMENT
# Задание соединения и параметров регистрации
# END COMMENT
my $dc = 'dc1';
my $user = 'administrator@mycorp.com';
my $passwd = 'Adminpasswd';
# BEGIN COMMENT
# Определение параметров поиска
# END COMMENT
my $base = "cn=users, dc=mycorp, dc=com";
my $scope = "subtree";
my $filter = "(&(objectclass=user)(objectcategory=user)(sn=Allen))";
my $ldap = Net::LDAP->new($dc) or die $@;
my $rc = $ldap->bind($user, password => $passwd);
die $rc->error if $rc->code;
# END CALLOUT A
# BEGIN CALLOUT B
my $search = $ldap->search(
  base => $base,
  scope => $scope,
  filter => $filter
);
die $search->error if $search->code;
# END CALLOUT B
# BEGIN CALLOUT C
foreach my $entry ($search->entries) {
  $entry->dump;
}
# END CALLOUT C
$ldap->unbind;
Как видно из программного кода в блоке B листинга 3, я использовал для осуществления запросов процедуру search(). Были заданы три параметра (т. е. base DN, scope и filter), которые мы рассмотрели выше. Дополнительно был задан параметр attrs, задающий массив атрибутов, которые необходимо вывести. Если не задать параметр attrs, операция поиска возвратит все атрибуты, имеющие значение. Если в результате поиска возвращается большое число атрибутов, возможно, кто-то захочет уменьшить количество возвращаемых данных, использовав для этого параметр.

Как видно из программного кода в блоке C листинга 3, я использовал процедуру entries() для последовательного отображения возвращаемых процедурой поиска данных. Этот метод возвращает массив объектов Net::LDAP::Entry. Для каждого из таких объектов я использовал процедуру dump() для вывода всех возвращаемых атрибутов. Если необходим доступ к определенному атрибуту, можно использовать процедуру get_value() так, как это было продемонстрировано в листинге 1.

Создание утилиты поиска в LDAP

Теперь, зная основы использования модулей Net::LDAP для выполнения операций поиска, давайте рассмотрим код простой утилиты командной строки ldapsearch.pl, показанной в листинге 4. При помощи этой утилиты можно производить запросы к AD. Ldapsearch.pl основывается на широко используемой утилите поиска в LDAP (ldapsearch), доступной в большинстве пакетов SDK для LDAP и серверов каталога LDAP (за исключением AD).

Листинг 4. Ldapsearch.pl
use strict;
use Net::LDAP;
use Net::LDAP::Filter;
use Getopt::Std;
# BEGIN CALLOUT A
# BEGIN COMMENT
# Получение и проверка заданных параметров.
# END COMMENT
my %opts;
getopt('bshpDwfa', \%opts);
my($err_str) = validate_options(\%opts);
if ($err_str) {
  print "ldapsearch.pl: $err_str\n";
  print usage();
  exit;
}
# END CALLOUT A
# BEGIN CALLOUT B
# BEGIN COMMENT
# Соединение, затем привязка к хосту
# END COMMENT
my $ldap = Net::LDAP->new($opts{h}, port => $opts{p} || 389) or die "$@\n";
my $rc->error if $rc->code;
# END CALLOUT B
# BEGIN CALLOUT C
# BEGIN COMMENT
# Выполнение поиска и отображение результатов
# END COMMENT
my $search = $ldap->search {
  base => $opts{b},
  scope => $opts{s},
  filter => $opts{f},
  attrs => [split /,/, $opts{a}],
);
die $search->error if $search->code;
foreach my $entry ($search->entires) {
  $entry->dump;
}
$ldap->unbind;
exit;
# END CALLOUT C
# BEGIN CALLOUT D
# BEGIN COMMENT
# Различные функции.
# END COMMENT
sub usage {
  return qq(
    usage: ldapsearch.pl [options]
    -b basedn     base dn for search
    -s scope      one of base,one, or sub (search scope)
    -h host       ldap server
    -D binddn     bind dn
    -w passwd     bind passwd (for authentication)
    -f filter     RFC-2254 compliant LDAP search filter
    -a attributes comma-separated list of attributes to retrieve
    -p port       port on ldap server
  );
}
sub validate_options {
  my $opts_ref = $_[0];
  foreach (qw(b s h D w f a)) {
    return("-$_ required") unless $opts_ref->{$_};
  }
  my $filter = Net::LDAP::Filter->new($opts_ref->{f});
  return("Bad search filter") unless ref $filter;
  return;
}
# END CALLOUT D
Основной код ldapsearch.pl по большей части не отличается от программного кода в листинге 3, кроме ключей командной строки, семь из которых являются обязательными. Вы уже знакомы с некоторыми из этих ключей: ключи -b, -s и -f задают, соответственно, параметры для поиска base DN (базовое полное имя), scope (границы, диапазон) и filter (фильтр). Дополнительные ключи -D и -w задают полное имя пользователя и пароль и применяются в целях авторизации. Ключ -h задает имя сервера LDAP. Ключ -a определяет атрибуты, которые нужно запросить. Необходимо задать атрибуты в виде списка, разделенного запятыми. Последний ключ -p является необязательным параметром. Рассмотрим его назначение.

Чтобы выполнить обработку ключей командной строки, я использовал модуль Perl с именем GetOpt::Std. Этот модуль обеспечивает базовую функциональность для обработки ключей. Поскольку большинство ключей обязательные, ldapsearch.pl включает в себя программный код, проверяющий наличие ключей. Этот программный код показан в блоке A листинга 4.

Блок B листинга 3 выделяет программный код, выполняющий установку соединения. Разница между этим кодом и кодом в листинге 2 — в использовании ключа port (-p). В командной строке можно использовать ключ -p для задания альтернативного порта, например 3268 для глобального каталога Global Catalog (GC). Если ключ -p не используется, сценарий по умолчанию задействует порт 389, который является стандартным портом LDAP.

Программный код в блоке C листинга 4 выполняет поиск и вывод соответствующих атрибутов. Этот программный код также похож на представленный в листинге 3, за исключением параметра attr в процедуре поиска, поскольку введен ключ -а, задающий атрибуты для вывода. Я использовал функцию split для включения в массив списка, разделенного запятыми.

На экране 1 показаны результаты работы сценария ldapsearch.pl. Первые три строки содержат параметры, используемые для запуска сценария из командной строки. Как видно по этим параметрам, я запускал сценарий для хоста dc1 (ключ -h). Сценарий выполняет поиск в контейнере cn=computers, dc=mycorp, dc=com (ключ -b) и в подконтейнерах (ключ -s) для всех объектов «компьютер», имеющих имя, начинающееся с app (ключ -f ). Результаты запроса определяют строки, следующие за параметрами. Поскольку я задал возвращение только атрибута cn для каждого из объектов (ключ -a ), результатом будет показ полного имени DN соответствующего объекта и его атрибута cn.

Экран 1. Результаты работы ldapsearch.pl
> perl ldapsearch.pl -b cn=computers,dc=mycorp,dc=com -s sub
-h dc1 -D rallen@mycorp.com -w MyPasswd
- "(&(objectclass=computer)(objectcategory=computer)(cn=app*)" -a cn
----------------------------------------------------------------------
dn:CN=APP01,CN=Computers,DC=mycorp,DC=com

cn:APP01
----------------------------------------------------------------------
dn:CN=APP02,CN=Computers,DC=mycorp,DC=com

cn:APP02
----------------------------------------------------------------------
dn:CN=APP03,CN=Computers,DC=mycorp,DC=com

cn:APP03
----------------------------------------------------------------------
dn:CN=APP04,CN=Computers,DC=mycorp,DC=com

cn:APP04

Добавление объектов

Использование Net::LDAP для добавления объектов имеет свои преимущества, например, листинг 5 содержит код, который добавляет объект John Doe. Блок A в листинге 5 выводит параметры, которые необходимо изменить для получения работающей программы. Для переменной $dc требуется задать контроллер домена, на котором будут выполняться операции добавления. Переменным $user и $passwd следует присвоить соответствующее имя и пароль, под которыми нужно подключиться к заданному контроллеру домена. Переменная $parent_dn должна содержать полное имя DN родительского контейнера, в котором предполагается разместить объект John Doe. Программный код после блока A в листинге 1 подключается к заданному контроллеру домена и использует введенные имя и пароль для соединения с ним.

Листинг 5. Программа, добавляющая объект Contact
# Программа, добавляющая объект Contact
use strict;
use Net::LDAP;
# BEGIN CALLOUT A
# BEGIN COMMENT
# Настройте для вашего окружения
# END COMMENT
my $dc = 'dc1';
my $user = 'administrator@mycorp.com';
my $passwd = 'Adminpasswd';
my $parent_dn = "cn=users,dc=mycorp, dc=com";
# END CALLOUT A
# BEGIN COMMENT
# Соединение и аутентификация
# END COMMENT
my $ldap = Net::LDAP->new($dc) or die "$@\n";
my $rc = $ldap->bind($user, password => $passwd);
die $rc->error if $rc->code;
# BEGIN CALLOUT B
# BEGIN COMMENT
# Добавление объекта contact John Doe.
# END COMMENT
$rc = $ldap->add("cn=mycontact, $parent_dn",
  attrs => [
    objectclass => 'contact',
    displayName => 'John Doe',
    sn => 'Doe',
    givenName => 'John',
    telephoneNumber => '555-123-4567',
  ]);
# END CALLOUT B
if ($rc->code) {
  print "Add failed: ", $rc->error, "\n";
}
else {
  print "Add successful\n";
}
$ldap->unbind;
Код блока B листинга 5 вызывает процедуру add(). Первым параметром является полное имя DN нового добавляемого объекта. Второй параметр attrs указывает на массив ссылок, содержащий атрибуты, которые присваиваются новому объекту. Необходимо включить некоторые обязательные параметры, например objectClass, которые не имеют значения по умолчанию, или добавить методы обработки на случай ошибки. Например, чтобы добавить объект «пользователь», необходимо, как минимум, задать user для параметра objectClass и имя пользователя для параметра sAMAccountName.

Метод add() возвращает объект Net::LDAP::Message, включающий процедуру code(), которая позволяет определить, были ли ошибки. Когда метод code() объекта Net::LDAP::Message возвращает значение 0, это означает, что контакт успешно добавлен. Если же метод code() возвращает иное значение, следовательно, была обнаружена ошибка, и метод error() отобразит соответствующее сообщение.

Листинг 5 добавляет в AD только один объект. Этот код можно расширить для добавления тысяч объектов. Можно использовать модули Perl Database Interface (DBI) для осуществления запросов к базе данных и пополнения AD восстановленной информацией. DBI-модули есть на сайте CPAN (http://www.perl.com/CPAN-local/modules/by-module/DBI).

Удаление объектов

Удалять объекты, используя Net::LDAP, даже проще, чем добавлять их. Все, что необходимо для проведения операции, это ввести полное имя объекта DN, который требуется удалить при помощи процедуры Net::LDAP delete(). Например, листинг 6 содержит код, удаляющий все объекты контактов в организационном подразделении ou=Contacts, dc=mycorp, dc=com.

Листинг 6. Код удаления объектов
# Код удаления объектов
use strict;
use Net::LDAP;
# BEGIN CALLOUT A
# BEGIN COMMENT
# Настройте для вашего окружения
my $dc = 'dc1';
my $user = 'administrator@mycorp.com';
my $passwd = 'Adminpasswd';
my $parent_dn = 'ou=Contacts, dc=mycorp, dc=com';
# END CALLOUT A
# BEGIN COMMENT
# Соединение и аутентификация
# END COMMENT
my $ldap = Net::LDAP->new($dc) or die "$@\n";
my $rc = $ldap->bind($user, password => $passwd);
die $rc->error if $rc->code;
# BEGIN CALLOUT B
# BEGIN COMMENT
# Найти все объекты типа контакт в родительском DN
# END COMMENT
my $search = $ldap->search(
  base => $parent_dn,
  scope => 'one',
  filter => "(objectClass=contact)",
  attrs => ['cn']
};
die $search->error if $search->code;
# END CALLOUT B
# BEGIN CALLOUT C
# BEGIN COMMENT
# Удалить все подходящие объекты
# END COMMENT
my $count = 0;
foreach my $entry ($search->entries) {
  $rc = $ldap->delete($entry->dn);
  if ($rc->code) {
    print "Delete failed for ", $entry->get_value('cn'), ": ", $rc->error, "\n";
  }
  else {
    $count++;
    print "Delete successful: " $entry->get_value('cn'), "\n";
  }
}
print "Total successfully deleted: $count=n";
# END CALLOUT C
$ldap->unbind;
Программный код блока A показывает параметры, которые необходимо задать. Программный код блока B листинга 6 осуществляет поиск объектов для удаления. Ранее мы рассмотрели, как используется метод search(), включая процесс установки каждого из параметров. В нашем случае мы устанавливаем параметр base для задания полного имени DN-контейнера, содержащего объекты, подлежащие удалению. Нам требуется удалить все объекты в этом контейнере, но не сам контейнер. Для этого присвоим параметру scope значение 'one', чтобы исключить начальное DN из поиска. Поиск будет вестись на уровне, следующем за начальным DN. Параметр filter содержит фильтр поиска, который задает просмотр объектов контактов. Параметр attrs содержит массив ссылок на отдельный атрибут 'cn'. Метод search() возвращает соответствующие объекты как массив объектов Net::LDAP::Entry.

Программный код блока C в листинге 6 использует процедуру объекта Net::LDAP::Entry dn() для получения DN для каждого из соответствующих объектов, так чтобы метод delete() мог удалить объект. Метод code() проверяет результат на предмет того, была ли обнаружена ошибка, и выводит соответствующее сообщение.

В некоторых случаях вместо удаления каждого из объектов в OU, возможно, понадобится удалить родительский OU и все его дочерние объекты за одну операцию. К сожалению, выполнить операцию удаления такого типа при помощи простой команды delete не удастся. Придется использовать расширение протокола Lightweight Directory Access Protocol (LDAP), называемое control, для информирования сервера об удалении отдельных контейнеров и всех его дочерних объектов. Применение расширения Net::LDAP controls выходит за рамки данной статьи и послужит темой для последующих публикаций. А если кому-то уже сейчас необходимо узнать о функциях controls, можно ознакомиться с документацией на сайте, посвященном perl-ldap (http://perl-ldap.sourceforge.net).

Изменение объектов

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

Подобно методам add() и delete(), метод modify() в качестве первого параметра имеет полное имя DN объекта, подлежащего изменению. Вторым является параметр, задающий тип выполняемой модификации. Метод modify() имеет несколько режимов работы и можно выбрать наиболее подходящий.

Параметр add. Этот параметр добавляет или устанавливает значение атрибута, который до этого не имел присвоенного значения. Можно использовать параметр add для добавления нового значения в атрибут, имеющий несколько параметров. Параметр add использует ссылки на хеш (функции хеша). Ключ хеша содержит имя атрибута, а значение ключа хеша содержит значение атрибута. Например, для того чтобы добавить атрибут mail и присвоить ему значение jdoe@mycorp.com, можно воспользоваться следующим программным кодом:
$ldap->modify($dn, add =>
  {mail => 'jdoe@mycorp.com'});
Параметр delete. Параметр удаляет определенное значение, связанное с атрибутом. Он также использует ссылку на хеш, содержащий соответствующие пары ключевых значений для удаления, подобно параметру add, либо ссылается на массив атрибутов, для которого нужно удалить все значения. Например, для удаления всех значений, связанных с атрибутами mail и displayname, можно воспользоваться следующим программным кодом:
$ldap->modify($dn, delete =>
  ['mail', 'displayname']);
Параметр replace. Параметр replace используется для изменения существующих параметров атрибута заданными значениями. Он задействует те же параметры, что и add. Так, например, если ввести неправильный адрес email при добавлении атрибута mail, можно изменить его на правильный следующим программным кодом:
$ldap->modify($dn, replace =>
  {mail => 'joe@mycorp.com'});
Параметр changes. Параметр используется для группировки набора параметров add, delete и replace в единый вызов. Параметр changes использует ссылки на массив, содержащий пары «имя параметра — значение». Например, для удаления значения, связанного с атрибутом mail, и добавления атрибутов givenname и sn, значения которых соответственно John и Doe, можно воспользоваться кодом:
$ldap->modify($dn,
  changes => [
    add => [givenname => "John"],
    add => [sn => "Doe"],
    delete => ['mail']]);
Листинг 7 использует параметры, о которых было рассказано, для изменения объекта «пользователь». Подобно методам add() и delete(), метод modify() возвращает объект Net::LDAP::Message, метод code() которого используется для определения факта возникновения ошибки.

Листинг 7. Программа, изменяющая атрибуты объекта "пользователь"
# Программа, изменяющая атрибуты объекта "пользователь"
use strict;
use Net::LDAP;
# Настройте для вашего окружения
my $dc = 'dc1';
my $user = 'administrator@mycorp.com';
my $passwd = 'Adminpasswd';
my $dn = "cn=jdoe, cn=users, dc=mycorp, dc=com";
# Соединение и аутентификация
my $ldap = Net::LDAP->new($dc) or die "$@\n";
my $rc = $ldap->bind($user, password => $passwd);
die $rc->error if $rc->code;
# Изменение нескольких атрибутов
pring "Setting givename, sn and mail...\n";
$rc = $ldap->modify($dn,
  changes => [
    add => [givename => "Johnny"],
    add => [sn => "Doh"],
    add => [mail => 'jdoe@mycorp.com'],
  ]
);
die $rc->error if $rc->code'
print "Changing givename to John...\n";
$rc = $ldap->modify($dn,
  replace => {givename => "John" });
die $rc->error if $rc->code;
print "Deleting the mail attribute...\n";
$rc = $ldap->modify($dn,
  delete => ['mail']);
die $rc->error if $rc->code;
print "Setting the telephoneNumber and sn...\n";
$rc = $ldap->modify($dn,
  changes => [
    add => [telephoneNumber => '555-123-4567'],
    replace => [sn => 'Doe'],
  ]
);
die $rc->error if $rc->code;
print "\nModifications successful\n";
$ldap->unbind;

Переименование и перемещение объектов

Чтобы иметь полный набор возможностей для управления объектами AD, необходима функция переименования и перемещения объектов. Метод Net::LDAP moddn() позволяет выполнить оба названных действия. Подобно методам, описанным выше, метод moddn() также имеет в качестве первого параметра полное имя объекта (DN), который предполагается переименовать или переместить. Второй параметр может состоять из одного или нескольких.

Параметры newrdn и deleteoldrdn. В AD вы идентифицируете объект по его полному имени DN, которое включает относительное имя relative distinguished name (RDN). RDN определяет имя объекта. Возьмем, например, объект с полным именем DN cn=jdoe, cn=users, dc=mycorp, dc=com. В этом случае относительным именем, RDN, будет jdoe. Параметр newrdn можно использовать для присвоения объекту нового RDN. Значение newrdn должно включать не только имя объекта, (например, jsmith), но и его идентификатор (например, cn=).

Листинг 8 содержит программный код, переименовывающий объект user cn=jdoe, cn=users, dc=mycorp, dc=com в cn=jsmith, cn=users, dc=mycorp, dc=com. Блок A листинга 8 выделяет код, включающий параметр newrdn. Этот код содержит в себе и параметр deleteoldrdn. Установив параметр deleteoldrdn в 1 (т. е. true), вы тем самым удаляете объект, имеющий старое имя RDN. Если же не включать параметр deleteoldrdn или установить его значение в 0 (т. е. false), старый объект, будучи переименованным или перемещенным, останется. Для того чтобы избежать такой ситуации, следует использовать параметр deleteoldrdn со значением 1.

Листинг 8. Программа, переименовывающая и удаляющая объект
# Программа, переименовывающая и удаляющая объект
use strict;
use Net::LDAP;
# BEGIN COMMENT
# Настройте для вашей среды
# END COMMENT
my $dc = 'dc1';
my $user = 'administrator@mycorp.com';
my $passwd = 'Adminpasswd';
my $dn = "cn=jdoe, cn=users, dc=mycorp, dc=com";
my $new_rdn = "cn=jsmith";
# BEGIN COMMENT
# Соединение и аутентификация
# END COMMENT
my $ldap = Net::LDAP->new($dc) or die "$@\n";
my $rc = $ldap->bind($user, password => $passwd);
die $rc->error if $rc->code;
# BEGIN CALLOUT A
# BEGIN COMMENT
# Переименование jdoe в jsmith
# END COMMENT
$rc = $ldap->moddn($dn, newrdn => $new_rdn, deleteoldrdn => 1);
# END CALLOUT A
if ($rc->code) {
  print "Error renaming user: ", $rc->error, "\n";
}
else {
  print "Succesfully renamed user\n";
}
$ldap->unbind;
Параметр newsuperior. Этот параметр применяется для перемещения объекта в другой родительский контейнер. Данным параметром задается для полного имени новый родительский контейнер. Например, листинг 9 содержит код, перемещающий все пользовательские объекты, атрибут department которых имеет значение Sales в OU Sales. Как показано в листинге 9, переменная $old_parent определяет родительский контейнер, в котором вы ведете поиск объектов, а переменная $new_parent задает родительский контейнер, в который предстоит перенести объекты. В листинге 9 в блоке B выделен код, ведущий поиск $old_parent для всех объектов пользователей с атрибутом department, равным Sales. Программный код в блоке C листинга 4 использует оператор foreach для перемещения всех подобных объектов в $new_parent.

Листинг 9. Программа, перемещающая объекты
# Программа, перемещающая объекты
use strict;
use Net::LDAP;
# BEGIN COMMENT
# Настройте для вашего окружения
# END COMMENT
my $dc = 'dc1';
my $user = 'administrator@mycorp.com';
my $passwd = 'Adminpasswd';
# BEGIN CALLOUT A
my $old_parent = "cn=users, dc=mycorp, dc=com";
my $new_parent = "ou=sales, dc=mycorp, dc=com";
# END CALLOUT A
# BEGIN COMMENT
# Подключение и аутентификация
# END COMMENT
my $ldap = Net::LDAP->new($dc) or die "$@\n";
my $rc = $ldap->bind($user, password => $passwd);
die $rc->error if $rc->code;
# BEGIN CALLOUT B
# BEGIN COMMENT
# Поиск всех объектов user, чей атрибут department = Sales
# END COMMENT
my $search = $ldap->search(
  base => $old_parent,
  scope => 'one',
  filter => "(&(objectclass=user)(objectcategory=Person)(department=Sales))",
  attrs => ['cn']
);
die $search->error if $search->code;
# END CALLOUT B
# BEGIN CALLOUT C
# BEGIN COMMENT
# Перемещение соответствующих пользователей в Sales OU
# END COMMENT
my $count = 0;
foreach my $entry($search->entries) {
  $rc = $ldap->modrdn($entry->dn,
    newrdn => 'cn=' . $entry->get_value('cn'),
    newsuperior => $new_parent,
    deleteoldrdn => 1);
  if ($rc->code) {
    print "Move failed for ", $entry->get_value('cn'), ": ", $rc->error, "\n";
  }
  else {
    $count++;
    print "Move successful: ", $entry->get_value('cn'), "\n";
  }
}
print "Total successfully moved: $count\n";
# END CALLOUT C
$ldap->unbind;
Итак, в этой статье мы рассмотрели, как использовать модули Net::LDAP для управления объектами в AD. При использовании этих модулей Perl можно выполнять все необходимые манипуляции для управления данными в AD.

Робби Аллен - Технический руководитель в компании Cisco Systems. MVP по Windows Server Directory Services. rallen@rallenhome.com

суббота, 19 июня 2010 г.

Миграция портала SharePoint Services в домен Active Directory

Предупреждение.

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

Постановка задачи.

Имеется веб-портал SharePoint Services 3.0, аутентификация пользователей в котором происходит по локальным учётным записям на самом сервере. Требуется ввести сервер в домен Active Directory и использовать для аутентификации на портале учётные записи из домена.

1. Получение информации об имеющихся на портале учётных записях

Запускаем MS SQL Enterprise Manager, открываем базу данных WSS_Content. Информация о пользователях находится в таблице UserInfo. Запускаем из Enterprise Manager'а инструмент SQL Query Analyzer, в которм выполняем следующий запрос:
select tp_SystemID, tp_Login, tp_Title
from UserInfo;
Полученную информацию копируем в таблицу Excel. В выбранных колонках находятся: SID пользователя, логин пользователя вместе с доменом в виде "ДОМЕН\Логин", строка описания пользователя. SID пользователя в базе данных хранится в двоичном виде, а Query Analyzer выводит его в шестнадцатеричном виде.

После этого в таблице должны иметься следующие столбцы:
A - шестнадцатеричный SID локальной учётной записи,
B - логин локальной учётной записи,
C - описание локальной учётной записи.

2. Добавляем в полученный список столбец с учётными записями пользователей из домена

Каждой учётной записи, используемой для входа на портал, нужно сопоставить учётную запись из домена. Возможно, на этом этапе понадобится завести в домене учётные записи недостающих пользователей.

После этого в таблице должны иметься следующие столбцы:
A - шестнадцатеричный SID локальной учётной записи,
B - логин локальной учётной записи,
C - описание локальной учётной записи,
D - логин доменной учётной записи.

3. Получение SID пользователей из домена

Берём программу user2sid Евгения Рудного по ссылке http://evgenii.rudnyi.ru/programming.html#sid2user. Копируем столбец D в текстовый файл, а в начале каждой строки добавляем команду user2sid. Копируем получившийся bat-файл в каталог с программой. Запускаем bat-файл, перенаправив вывод из него в другой текстовый файл. В полученном текстовом файле убираем всё, кроме SID'ов, так чтобы в каждой строчке файла было по одному SID'у. Информацию из текстового файла добавляем в столбец E файла Excel.

После этого в таблице должны иметься следующие столбцы:
A - шестнадцатеричный SID локальной учётной записи,
B - логин локальной учётной записи,
C - описание локальной учётной записи,
D - логин доменной учётной записи,
E - SID доменной учётной записи.

Будьте внимательны! В списке должны присутствовать ровно столько SID'ов пользователей, сколько их имелось в первоначальном столбце. Также они должны следовать точно в таком же порядке, как в Excel-файле. Если что-то идёт не так, нужно поправить Excel-таблицу и bat-файл и повторить этот этап снова.

4. Конвертирование доменных SID в шестнадцатеричный вид

Для понимания того, каким образом SID хранится в двоичном виде в системе, можно воспользоваться следующей статьёй: Microsoft Security Descriptor(SID)Attributes.

Пишем на PHP сценарий для конвертирования SID:
<?

  function swap($hexdword)
  {
    $res = "";
    for($i = 6; $i >= 0 ; $i -= 2)
      $res .= $hexdword[$i] . $hexdword[$i+1];
    return $res;
  }

  function sid2hex($sid)
  {
    $sidl = explode("-", $sid);

    $hex_sid = "0x" .
      sprintf("%02X", $sidl[1]+0) .
      sprintf("%02X", $sidl[2]+0) .
      "000000000005";

    for($i = 0; $i < $sidl[2]; $i++)
      $hex_sid .= swap(sprintf("%08X", $sidl[3+$i]+0));

    return $hex_sid;
  }

  function hex2sid($hex_sid)
  {
    $hex_sid = trim($hex_sid);
    $sidl[0] = "S";
    $sidl[1] = hexdec(substr($hex_sid, 2, 2));

    $sidl[2] = hexdec(substr($hex_sid, 4, 2));

    for($i = 0; $i < $sidl[2]; $i++)
      $sidl[3+$i] = hexdec(swap(substr($hex_sid, 18+$i*8, 8)));

    $sid = implode("-", $sidl);

    return $sid;
  }

  $sids = "S-1-5-21-583367659-4273102991-479599032-4176
S-1-5-21-583367659-4273102991-479599032-4450";

  $sidsl = explode("\n", $sids);

  foreach ($sidsl as $sid)
    echo sid2hex($sid) . "<br>";
?>
Для того, чтобы быть уверенным в правильности написанных функций, я написал их пару. Проверив, что преобразование SID в шестнадцатеричный вид и обратно, не искажает его, я удостоверился, что функции написаны правильно.

В вышеприведённом сценарии я оставил только пару SID'ов. Вам следует вставить на место этих двух SID'ов содержимое столбца E. Полученный после выполнения сценария результат нужно скопировать в столбец F.

После этого в таблице должны иметься следующие столбцы:
A - шестнадцатеричный SID локальной учётной записи,
B - логин локальной учётной записи,
C - описание локальной учётной записи,
D - логин доменной учётной записи,
E - SID доменной учётной записи,
F - шестнадцатеричный SID доменной учётной записи.

5. Получение SQL-запросов для миграции портала на доменные учётные записи

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

Теперь мы можем написать в Excel'е формулы, которые сгенерируют нам SQL-запросы, необходимые для внесения необходимых изменений в БД SharePoint Services.
G ="update UserInfo set tp_Login = '" & D2 &"', tp_SystemID = " & F2 & " where tp_Login = '" & B2 &"' "

H ="update AllUserData set nvarchar3 = '" & D2 &"' where nvarchar3 = '" & B2 &"' "
Чтобы не бросаться голой задницей на амбразуру, подготовим SQL-запросы для отката навороченного:
I ="update UserInfo set tp_Login = '" & B2 &"', tp_SystemID = " & A2 & " where tp_Login = '" & D2 &"' "

J ="update AllUserData set nvarchar3 = '" & B2 &"' where nvarchar3 = '" & D2 &"' "
После этого в таблице должны иметься следующие столбцы:
A - шестнадцатеричный SID локальной учётной записи,
B - логин локальной учётной записи,
C - описание локальной учётной записи,
D - логин доменной учётной записи,
E - SID доменной учётной записи,
F - шестнадцатеричный SID доменной учётной записи,
G - SQL-запрос для обновления информации в таблице UserInfo,
H - SQL-запрос для обновления информации в таблице AllUserData,
I - SQL-запрос для отката обновления таблицы UserInfo,
J - SQL-запрос для отката обновления таблицы AllUserData.

Создаём два текстовых файла - update.txt и rollback.txt. В первый копируем столбцы G и H, а во второй - I и J.

6. Миграция

Для миграции нужно выделить перерыв обслуживания примерно в 15 минут. За это время можно будет выполнить SQL-запросы из файла update.txt и ввести компьютер в домен. На случай если что-то пойдёт не так, лучше увеличить паузу до 30 минут, чтобы вывести компьютер из домена и выполнить SQL-запросы из файла rollback.txt.

На случай же если же что-то пойдёт совсем не так, следует заготовить резервную копию базы данных. Перед снятием резервной копии нужно перевести базу данных портала в режим "Только чтение" через "Центр администрирования" SharePoint Services, вкладку "Управление приложениями", пункт "Квоты и блокировки семейства узлов", а затем снять резервную копию средствами MS SQL Enterprise Manager. После восстановления следует не забыть восстановить режим "Нет блокировки".

Я выполнял обновление из дома, глубоким вечером, разумеется проработав в рабочее время сценарий миграции и текстовые файлы с SQL-запросами.

7. Кое-что ещё

Дополнительно, я поменял учётную запись администратора SharePoint Services в "Центре администрирования" на доменную. Поменял я её в базе данных SharePoint_Admincontent_многобукв в таблицах AllUserData и UserInfo. Это уже не столь критично и не столь сложно. Администраторов мало и они, в отличие от пользователей, могут выдержать и больший перерыв в обслуживании.

пятница, 18 июня 2010 г.

Применение групповых политик

На днях боролся с групповыми политиками Active Directory.

У нас на работе действует стандарт по обеспечению безопасности, предписывающий использовать сложные длинные пароли и периодически менять их (конкретные требования в этом изложении роли не играют). В то же время, есть несколько служебных учётных записей, которые используются большим количеством людей. Например, для доступа к порталу Share Point из агентств, используются учётные записи agent и readonly. Эти учётные записи имеют простые пароли, совпадающие с именем пользователя.

Сейчас эти учётные записи заведены на том же компьютере, где установлен Share Point. Этот компьютер не введён в домен. Чтобы упростить жизнь людям, хочется использовать учётные записи из домена Active Directory. Люди, работающие под той же учётной записью, под которой они входят на портал, смогли бы больше не задумываться о пароле и автоматически попадать на портал под своей учётной записью. Пароль личной учётной записи постоянно менялся бы синхронно со сменой пароля "на вход в компьютер", чем достигалась бы повышенная безопасность.

Перед вводом сервера в домен и переводом портала на использование учётных записей из Active Directory нужно завести недостающих пользователей портала в домене. Как минимум, это учётные записи agent и readonly, обладающие простыми паролями. Соответственно, для того, чтобы их можно было завести с теми же паролями, необходимо смягчить политику безопасности паролей. Для этого я создал в Active Directory новое подразделение, для которого создал новую групповую политику "Простые пароли". Я применил её к подразделению, и попробовал завести пользователей. Но пользователей завести не удалось, т.к. их пароли не удовлетворяли требованиям безопасности.

Я посмотрел результирующую групповую политику и увидел следующее:


При просмотре любого из параметров в окошке свойств параметра выводится следующий текст:



Цитирую, чтобы мой пост легче находился поиском: "GPO, расположенные выше, имеют более высокий приоритет. Процессор обработки политики не пытался настроить параметр. Дополнительная информация приведена в %windir%\security\logs\winlogon.log на целевом компьютере."

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

Дело в том, что политика паролей (как и все политики учетных записей) применяется к доменным учетным записям, которые находятся на контроллерах домена, а не на рабочих станциях. Поэтому всякие запреты наследования бесполезны, т.к. применяются не там.

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

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

Так что ваша задача не имеет решения в рамках одного домена.

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

Причём отвечающие говорят об этом, как о достаточно известном fuck'те. Как я понимаю, обойти это можно только поменяв быстренько, пока никто не заметил, политику безопасности, завести нужных пользователей или поменять пароли, а затем так же быстро и незаметно вернуть прежнюю политику =D Вам смешно? Мне тоже. Хотя для кое-кого, это всё было бы смешно, если бы не было столь грустно. Например, для тех, кто не имеет возможности поменять политику домена - для администраторов подразделений.