Однажды для нашего корпоративной информационной системы понадобилось оперативно менять сохраненыe пароли пользователей, чьи имена были импортированы из LDAP.



Традиционно корпоративные клиенты для изменения пароля пользутся готовыми системами типа: Microsoft Forefront Identity Manager (FIM), Oracle Identity Manager, IBM Security Identity Manager и прочие другие.

Чтобы не умереть на работе, занимаясь изучением разнообразного народного корпоративного творчества, пытаясь получить изменения пароля от хххIМенеджеров, лучше сразу обратится к контроллеру домена, куда новый пароль так или иначе прийдет.

Тем более что Microsoft AD сервис позволяет иметь раcширения. И все бы хорошо, пока некоторые кастомеры не сказали — все хорошо что вы большая и известная международная корпорация и мы десятки лет пользуемся вашим оборудованием и программным обеспечением, но ставить на наш сервер с AD контроллером ваш криворукий специальный софт не будем, — только хардкор решения от Microsoft.



Основными такими возможностями обладает Password Change Notification Service (PCNS) Configuration Utility. Эта утилита входит в состав FIM. Начав изучать как мне добится желаемой цели, я перечитал все возможные статьи от Microsoft и с перепугу недопонимания, как это работает поставил сам FIM, имеюший порядка десятка зависимостей от различных продуктов, включая SharePoint, который еше сам имеет пару десятков зависимостей, в числе которых MSSQL server, голосовой поиск и массу прочего ненужного и тяжеловесного софта, ради то чтобы понять, что в итоге FIM — это две примитивных HTML странички, каждая из которых имеет пару примитивных контролов для сброса пароля.

И чтобы поставить все это требуется некислый сервер (Microsoft рекомендует 3-4 сервера для этого) и примерно рабочий день. Угробив кучу времени и опять перечитав половину интернета, я понял что в представлении Microsoft есть как минимум 3! пути движения паролей:
— IM то DC (xxx identification manager to Domain Controller);
— Mail system to DC (Exchange/GroupWise/Domino to Domain Controller);
— DC to Mail system

То что я хотел в этот список не входило, поскольку ни IM ни почтовые системы мне не хотелoсь трогать, в виду чрезвычайного разноообразия оных. Все это движение паролей проходило через FIM Synchronization Service и на аппаратном уровне для всех моих безобразий хватало одного сервера минимальной конфигурации.

Формально FIMSS представляет из себя продвинутую среду для автоматизированной синхронизации данных чего угодно с чем угодно (в разумных пределах конечно) и имеет ряд готовых агентов для DC и всех известных почтовых агентов, и конечно ряда пользовательских расширений.

Microsoft предлагает несколько весьма запутанных step by step руководств, но осилить вот так блондинке с улицы будет весьма непросто, и более того, — это все не то что нужно для моего простого, как я думал случая. Вдоволь намучавшись с примерами Еxtension Projects и неполучив нужного мне результата, вновь обратил свой взор в Google в поисках нужного решния — и о чудо — нашелся один таки один! (скорее всего я опять плохо искал) пример где парнишка на бэсийке что-то сделал и оно работает (с его слов)! Самое главное скормить гуглю правильное кодовое слово: IMAPasswordManagement.

В обшем, получив волшебный пендель в заданном направлении я быстро наваял похожую конфигурацию, почeму похожую, потому что повторив это, — оно не заработало, пока я вдоволь не намучался, и, методом научно тыка не понял, как работают основные принципы передачи пароля.

Что бы пароль приходил в мой код написанный на C#, а там я могу резвится делать с ним что хочу — надо:

1. Инсталлировать FIM Synchronization Service и в нем установить возможность синхронизации паролей:


MVExtension.DLL как и пользовательские агенты синхронизации генерируются (тексты на C# или на VB.NET) прямо из FIMSS.

RPC сервис должен быть разрешен (используется для связи с Password Change Notification Service (PCNS) Configuration Utility).

2.Завести в FIMSS 2 агента — оба уже готовые:



Все агенты имеют 3 основных свойства — импортировать, экспортироват и синхронизировать. В нашем случае достаточно 2: импорт и синхронизация.
Почему — скажу позже.

1. DCAgent: импортирует список пользователей из DC
2. RNAgent: импортирует список пользователей из нашего самописного корпоративного сервиса (где уменьшенный список тех же пользователей, то есть по именам они совпадают — мой агент получает только имена, полученные из LDAP). Этот агент создается на базе SQL агента (то есть данные (список пользователей) получает из MSSQL)

Тут небольшое вступление — пароль будет передатся только если имена из обоих агентов совпадут по имени.
Чтобы это произошло у агентов FIMSS есть профайлы где настраиваются, a потом запускаются профайлы с нужными командами — сначала импорт, потом синхронизация.

Внутри себя FIMSS держит все в MSSQL базе и если вам не нужно заботится o ограниченном списке из вашего софта, то можно порекомендовать (думаю Microsoft будет отговаривать от такого хака) читать список пользователей прямо из внyтренних таблиц FIMSS, где список был создан агентом DCAgent: select accountName from [FIMSynchronizationService].[dbo].[mms_metaverse]:



Другой путь — это читать список пользователей прямо из домашней базы, но тут мне доступ тоже ограничили, так что только через dll, которое криптует все данные со страшной силой.

Настройка агентов тоже нетривиальное дело и наверное потребует отдельной статьи, но ничего такого чего нет в статьях от Microsoft. Укажу только несколько моментов, которые критически важны:

1. Чтобы RNAgent мог дергать наш Password Extension (текст на C# тоже генерится из FIMSS) нужно его указать:



Этот в метод позволяет получить имя аккаунта и пароль в открытом виде и вызвать метод из нашей DLL на С++ которая шифрует результат и передает REST сервису нашего корпоративного сервера. Бинго.

        public void SetPassword( CSEntry csentry,  string  NewPassword )
        {
            Log(String.Format("SetPassword entered: [{0}] is : [{1}]", csentry.DN.ToString(), NewPassword));

            UNICODE_STRING User = InitLsaString(csentry.RDN);
            UNICODE_STRING Pwd = InitLsaString(NewPassword);

            SendPasswordToMyServer(User, Pwd);
        }



2. DCAgent: Указать кому передаем пароль


После того как агенты созданы нужно что бы их кто-то пинал, чтобы импорт и синхронизация происходили регулярно. Мы для себя установили что раз в сутки вполне достаточно, но можно и сделать раз 5 минут. Основная проблема, что в большой компании с десятками тысяч сотрудников импорт из DC может идти очень долгое время и поэтому для регулярного импорта лучше использовать не Full Import а Delta Import. Microsoft предлагает следуюшее решение — дергать профайлы с помошью TASK SCHEDULER.
Если сходите по ссылке то сможете увидеть что в диалоге создания профайла можно сгенерить C#/VB который и запускать по расписанию.

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

То есть на той же машине иметь C# Windows Service, который будет по таймеру читать xml, раскодировать его и вставлять в нашу базу-табличку, из которой RNAgent уже и будет читать.

Основной код сервиса
        protected bool Process()
        {
            IntPtr ptrUserList = new IntPtr();
            bool result = false;
            try 
            {
                ptrUserList = GetUserList();

                string  userList = StringFromNativeUtf8(ptrUserList);

                 int cnt_imported = -1;

                 result = SaveAccounts(userList, ref cnt_imported);          // LDAP users will appear in [RNService].[dbo].[CoreAccount]
                 result = result ? DCAgent(m_dcagent_guid) : false;          // sync users from DC
                 result = result ? RNAgent(m_rnagent_guid) : false;          // sync users from [RNService].[dbo].[CoreAccount]
                  eventLogRN.WriteEntry("Imported users: " + cnt_imported );
            }
            catch (Exception ex)
            {
                eventLogRN.WriteEntry("Process: " + ex.Message, EventLogEntryType.Error);
                return false;
            }
            DisposeUserList(ptrUserList);
            
            m_timer.Interval = m_servicePollInterval;  // use interval from registry

            return result;
        }
        public bool SaveAccounts(string userList, ref int cnt_imported)
        {
            string con_str = @"Data Source=" + m_serverName + ";Initial Catalog=RNService;Integrated Security=True";
            cnt_imported = -1;

            using (SqlConnection cnn = new SqlConnection(con_str))
            {
                cnn.Open();

                    using (SqlCommand cmd = new SqlCommand())
                    {
                        cmd.Connection = cnn;
                        cmd.CommandType = CommandType.Text;
                        cmd.CommandText = "TRUNCATE TABLE  CoreAccount;"
                                        + " INSERT INTO CoreAccount SELECT X.C.value(N'@name', N'nvarchar(448)') FROM "
                                        + " (SELECT @data AS XML_DATA) DATA CROSS APPLY DATA.XML_DATA.nodes(N'/response/users/user') as X(C); "
                                        + " SELECT @@ROWCOUNT AS usercnt; ";

                        cmd.Parameters.Add("@data", SqlDbType.Xml);

                        try
                        {
                                cmd.Parameters[0].Value = userList;

                                SqlDataReader SqlDataReader = cmd.ExecuteReader();

                                if (!SqlDataReader.IsClosed && SqlDataReader.HasRows)
                                {
                                    if( SqlDataReader.Read() )
                                    {
                                        cnt_imported = SqlDataReader.GetInt32(0);
                                    }
                                }
                        }
                        catch (Exception ex)
                        {
                            eventLogRN.WriteEntry("UpdateCoreAccounts: " + ex.Message, EventLogEntryType.Error);
                            cnn.Close();
                            return false;
                        }
                }
                cnn.Close();
                return true;
            }
        }




В этом же сервисе и дергать профайлы агентов которые мы сгенерили, чем избавим наших клиентов мучений с TASK SCHEDULER.
В моем случае они просто вынимают гуиды профайлов из скрипта и кладут в регистри.

        public bool Agent(string agentName, string guid)
        {
            if (guid == null_guid)
            {
                return true;            // required manual syncronization
            }
            try
            {
                ConnectionOptions opt = new ConnectionOptions();
                opt.Authentication = AuthenticationLevel.PacketPrivacy;
                ManagementScope myScope = new ManagementScope("root\\MicrosoftIdentityIntegrationServer", opt);
                string sQuery = "GUID='{" + guid + "}'";
                SelectQuery myQuery = new SelectQuery("MIIS_ManagementAgent", sQuery);
                ManagementObjectSearcher searcher = new ManagementObjectSearcher(myScope, myQuery);
                foreach (ManagementObject ma in searcher.Get())
                {
                    eventLogRN.WriteEntry(agentName + ".Execute( \"ImportSync\" )...");
                    ma.InvokeMethod("Execute", new object[1] { "ImportSync" });
                }
            }
            catch (Exception ex)
            {
                eventLogRN.WriteEntry( ex.Message, EventLogEntryType.Error);
            }
            return true;
        }

Поделиться с друзьями
-->

Комментарии (9)


  1. Razaz
    15.07.2016 16:11

    Не понял изначальной проблемы, но разраешать кому либо слушать пароли я бы в принципе запретил.
    Вместо синхронихации впендюрил бы федерацию и закрыл тему копирования учеток.


    1. BalinTomsk
      15.07.2016 16:36

      Основная проблема которая решается — пользователь должен иметь доступ к принтерам со своих компютеров в нашем сервере, и не только к принтерам но и к MFP устройствам, внутри которых тоже есть и windows и linux и прочие огороды, так что мы вынуждены фактически дублировать некоторые функции AD.


      1. Razaz
        15.07.2016 16:55

        А как ваша система обеспечивает доступ?
        Просто в статье вы смотрели на IDM решения, а это только одна часть паззла. Вторая часть — IAM решения.
        Основной смысл, что пароль не доступен для передачи третьим сторонам.

        Надо смотреть протоколы по которым это все работает. Возможно приведете какой-то конкретный юзкейс?


        1. BalinTomsk
          15.07.2016 20:20

          У нас коробочный продукт, поставляемый с железом, поэтому не стоят IDM /IAM задачи.

          Поэтому и пришлось пользоватся не готовым конструктором в составе FIM, а городить переброску пароля методом велосипедостроения.

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


          1. Razaz
            15.07.2016 20:52

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


  1. VitalKoshalew
    16.07.2016 01:07

    Правильно ли я понимаю следующие пункты?
    1. Вы храните пароли некоторого числа пользователей в plain text в SQL базе на сервере.
    2. Данная система работает с правами, достаточными для смены пароля любого пользователя, да плюс к тому для перехвата пароля любого пользователя, включая администраторов домена и.т.д.
    3. Вам запретили вешать свою самописную DLL в качестве расширения на событие смены пароля, потому вы вешаете сгенерированную по шаблону DLL, которая вызывает другую сгенерированную DLL, которая всё равно вызывает вашу самописную DLL.

    Как я понял из вашего описания, это всё нужно, чтоб ваш саппорт мог подключиться к принтеру. LDAP ваши корпоративные принтеры поддерживают? Почему не сделать один простой сервис на стороне клиента, который будет по таймеру получать зашифрованный список паролей вашего саппорта с вашего сервера и устанавливать их для ваших пользователей? Такой сервис будет иметь в AD ровно те права, которые нужно, и ни шага дальше. Более того, такой сервис можно поставлять в виде ASPX.CS или даже PowerShell скрипта, там пару строк будет — легко проверить.

    Зачем вообще передавать пароли перехваченные в домене клиента на ваш сервер? Какое это отношение имеет к поставленной задаче?


    1. BalinTomsk
      16.07.2016 07:56

      — в plain text в SQL базе на сервере.

      непонятно почему вы так решили. Если уж трафик криптуем, то и пароли тоже.

      --Данная система работает с правами, достаточными для смены пароля любого пользователя

      тоже непонятно откуда сделан такой вывод.

      ---для перехвата пароля любого пользователя, включая администраторов домена

      ну только если он решит стать пользователем системы. Как правило я адмниские права использую лишь в необходимых случаях.

      ---которая всё равно вызывает вашу самописную DLL

      Все пишут свои самописные изделия, включая Microsoft.

      --Почему не сделать один простой сервис на стороне клиента

      Наш сервис и так устaнавливается на стороне клиента. Было бы странно чтобы если купили компьютер на работу и не дали всех ему небходимых прав для работы в сети. Наш сапор лишь устанавливает оборудование и обучает персонал как с ним работать.


      1. VitalKoshalew
        16.07.2016 09:47

        Забыл поблагодарить за статью, спасибо!

        в plain text в SQL базе на сервере.

        непонятно почему вы так решили. Если уж трафик криптуем, то и пароли тоже.

        Из описания в статье я предположил, что стандартный агент SQL Server использует SQL в том числе для пароля. Сейчас глянул документацию на сайте Microsoft, пароль — единственное поле, которое в SQL не хранится, а передаётся сразу в extension DLL. Собственно, о чём и был вопрос.

        Данная система работает с правами, достаточными для смены пароля любого пользователя

        тоже непонятно откуда сделан такой вывод.

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

        для перехвата пароля любого пользователя, включая администраторов домена

        ну только если он решит стать пользователем системы. Как правило я адмниские права использую лишь в необходимых случаях.

        Я вычитал, что в настройках PCNS на контроллерах домена (точнее — в самой базе AD) задаётся группа (inclusion security group — MS-MIIS-PCNS-TargetInclusionSID), пароли членов которой и будут перехватывать PCNS. То есть администраторы домена могут ограничить возможности перехвата. Я так понимаю, PCNS ставят сами администраторы, они же его и настраивают?

        которая всё равно вызывает вашу самописную DLL

        Все пишут свои самописные изделия, включая Microsoft.

        Собственно, это было по поводу "ставить на наш сервер с AD контроллером ваш криворукий специальный софт не будем, — только хардкор решения от Microsoft."
        Но если исходить из логики, что именно на DC как раз ставится Microsoft-овский PCNS, то тогда требование соблюдено. Самописная DLL — на отдельном сервере.

        Почему не сделать один простой сервис на стороне клиента

        Наш сервис и так устaнавливается на стороне клиента. Было бы странно чтобы если купили компьютер на работу и не дали всех ему небходимых прав для работы в сети. Наш сапор лишь устанавливает оборудование и обучает персонал как с ним работать.

        Да, как я уже сказал выше, из документации стал понятен общий вид решения. Та часть, которая отвечает за изменение паролей в AD по сигналу с вашего удалённого сервера — как раз то, о чём я говорил.

        Не в обиду, на самом деле очень интересная статья, но нюансы со стороны разработчика есть, а со стороны системного администратора / безопасности — нет. Только чтение документации расставило точки над i.

        И всё же — зачем понадобилось перехватывать пароли пользователей клиента и передавать их на удалённый сервер компании, оказывающей поддержку оргтехники?

        И что, если не секрет, с этими перехваченными паролями делают на стороне поставщика? Производят такую же синхронизацию — записывают в свой AD, при этом пароль хэшируется? Или где-то хранят в обратимом виде?

        Ещё раз спасибо за статью!


        1. BalinTomsk
          18.07.2016 06:37

          ---Я так понимаю, PCNS ставят сами администраторы, они же его и настраивают?

          Да, нас туда и не пустят. Только машина с нашим сервисом, остальное в руках компанейских админов.

          --зачем понадобилось перехватывать пароли пользователей клиента и передавать их на удалённый сервер компании, оказывающей поддержку оргтехники?

          никаких удаленных серверов нет — на картинке все живет в одном домене комании где ставится наш сервер. Принтера живут с виндами и линуксами внутри и чтобы обеспечить прозрачный доступ и нужен пароль.