суббота, 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 Вам смешно? Мне тоже. Хотя для кое-кого, это всё было бы смешно, если бы не было столь грустно. Например, для тех, кто не имеет возможности поменять политику домена - для администраторов подразделений.

четверг, 10 июня 2010 г.

Настройка IPSEC туннеля между Cisco и FreeBSD

Не очень люблю дублировать лишний раз информацию, поэтому просто сошлюсь на статью, по которой я настроил IPSec-туннель между Cisco и FreeBSD: Настройка IPSEC туннеля между Cisco и FreeBSD

От себя лишь хочу добавить вот что.

1. Если все соединения настроены по данной статье, тогда sainfo в конфигурации racoon будет везде совпадать. А это значит, что вместо повторения одной и той же секции, можно написать секцию sainfo один раз, заменив секции вида:
sainfo subnet 10.5.3.0/24 any address 10.11.16.0/23 any {
  pfs_group 2;
  lifetime time 24 hour;
  encryption_algorithm aes;
  authentication_algorithm hmac_sha1;
  compression_algorithm deflate;
}
на одну секцию вида
sainfo anonymous{
  pfs_group 2;
  lifetime time 24 hour;
  encryption_algorithm aes;
  authentication_algorithm hmac_sha1;
  compression_algorithm deflate;
}
2. Реальные таблицы маршрутизации бывают много сложнее выдуманных книжных.

Во всех найденных мной статьях между собой связываются два не пересекающихся диапазона IP-адресов. Например, сети 192.168.0.0/24 и 172.16.1.0/25 или 10.0.1.0/24 и 10.0.2.0/24 и т.п.

Но реальные ситуации могут оказаться гораздо сложнее. Например, на одном из маршрутизаторов под управлением FreeBSD могут оказаться три интерфейса:
  1. Интернет-интерфейс, через который идёт трафик по-умолчанию, и трафик на сеть 192.168.1.0/24,
  2. Локальный интерфейс, к которому подключена сеть 10.0.0.0/25,
  3. Некий дополнительный интерфейс, через который нужно направлять шифрованный трафик из локальной сети в сеть 192.168.0.0/16.
LAN 10.0.0.0/25
      |
      |
  ____|______
 |           |
 |  FreeBSD  |------> Internet, LAN 192.168.1.0/24
 |___________|
      ||
      || IPSec
 _____||_____
|            |
|   Cisco    |
|____________|
      |
      |
      |  
LAN 192.168.0.0/16
Что мы видим в этом случае? Два пересекающихся диапазона IP-адресов: 192.168.1.0/24 и 192.168.0.0/16. В файле ipsec.conf FreeBSD можно указать лишь сети, для которых будет осуществляться шифрование, но нельзя указать интерфейс, для которого действует это правило. В итоге может получиться, что будет шифроваться весь трафик из сети 10.0.0.0/25 в сеть 192.168.0.0/16, включая трафик на сеть 192.168.1.0/24, уходящий через Интернет-интерфейс, где никто не ожидает, что он будет зашифрован.

Или другой подобный случай. Есть большая IPSec-сеть, в которой филиальные маршрутизаторы FreeBSD соединяются только с центральным офисом компании. Маршрутизаторов много, скажем три-четыре десятка, за каждым из них закреплён свой диапазон 10.0.x.0/24. Что делать, если к центральному офису будет подключен ещё один филиальный маршрутизатор? Нужно будет прописывать эту сеть в политики шифрования каждого из остальных филиальных маршрутизаторов? А не устанешь?
LAN 10.0.x.0/24
      |
      |
  ____|______
 |           |
 |  FreeBSD  |
 |  10.1.0.x |
 |___________|
      ||
      || IPSec
 _____||_____           ___________
|            |         |           |
|   Cisco    |  IPSec  |  FreeBSD  |
|  10.1.0.1  |=========| 10.1.0.y  |--------LAN 10.0.y.0/24
|____________|         |___________|
      ||
      || IPSec
  ____||_____
 |           |
 |  FreeBSD  |
 | 10.1.0.z  |
 |___________|
      |
      |
      |
LAN 10.0.z.0/24
В таком случае было бы хорошо настроить на маршрутизаторах филиалов некое подобие маршрута по умолчанию. Например, прописать, что филиальный маршрутизатор должен шифровать трафик между сетями 10.0.0.0/8 в центральном офисе и 10.0.x.0/24 в филиале. Но если написать так в ipsec.conf, то маршрутизатор будет шифровать трафик между своей локальной сетью 10.0.x.0/24 и собой 10.0.x.1, поскольку его собственный адрес 10.0.x.1 формально тоже находится в "большой сети" 10.0.0.0/8. А ведь FreeBSD может быть не только маршрутизатором, а и, например, почтовым сервером. В таком случае почтой в локальной сети филиала пользоваться не смогут.

Именно на такую ловушку я и попался. Хорошо, что я перед настройкой на реальном оборудовании собрал тестовый стенд, на котором и выявил такую проблему.

В подобных случаях можно исключить некую подсеть из большой сети следующим образом:
spdadd 10.0.8.0/24 10.0.8.0/24 any -P out none;
spdadd 10.0.8.0/24 10.0.8.0/24 any -P in none;
spdadd 10.0.8.0/24 10.0.0.0/8 any -P out ipsec esp/tunnel/10.1.0.8-10.1.0.1/unique;
spdadd 10.0.0.0/8 10.0.8.0/24 any -P in ipsec esp/tunnel/10.1.0.1-10.1.0.8/unique;
В этом примере мы исключаем из шифрования сети 10.0.0.0/8 подсеть 10.0.8.0/24.