IBM MQ + Integrated Windows Authentication (NTLM/Kerberos) + Distributed Transactions (MS DTC), часть 1

от автора

Задача

В одном из проектов пришлось подойти к незаслуженно заброшенному и игнорируемому снаряду. Среда функционирования:

  • Активная директория Windows.

  • Менеждер очередей IBM MQ 9.1.5

  • СУБД Microsoft SQL Server 2016

  • Сервер приложений на .NET

Всё под управлением ОС MS Windows Server 2016, само собой x64. Приложениям надо инициировать распределенные транзакции между IBM MQ и MS SQL, да ещё и запрещается хранить где-либо логины с паролями и ограничения на криптографию. А значит только NTLM/Kerberos. NTLM у нас тоже запрещён, но это уже мелочи. За координацию распределенных транзакций отвечает служба MS DTC (Distributed Transaction Coordinator).

Лирика, жалобы на несовершенство мира

Отношение к IBM MQ на момент начала этих приключений было неплохое. Верилось, что это всё повидавший, хорошо отлаженный и матёрый продукт корпоративного класса. первые сомнения появились когда вместо простой и понятно пошаговой инструкции в документации к IBM MQ нашлись «туманные, плохо проветриваемые переулки, заполненные токсичными отбросами». Собственно, вниманию наивного читателя предлагается парочка статей
https://www.ibm.com/docs/en/ibm-mq/9.2?topic=windows-sspi-channel-exit-program
https://www.ibm.com/docs/en/ibm-mq/9.2?topic=multiplatforms-using-sspi-security-exit-windows
из которых становится ясно, что ничего не ясно.

Первое впечатление от этих статей можно кратко сформулировать так: «Простота — не наш метод. IBM MQ должен занимать в вашей жизни гораздо больше места, чем занимает сейчас. IBM MQ это не какй-то там инструмент. Это образ жизни, это философия, это путь!».

Основательно погрузившись в сладостный мир IBM MQ, отринув казульщину «бац, бац и в продакшн», раскаявшись в желании получить результат быстро и сполна насладившись непреступной академичностью документации, выяснил:

Для IWA-аутентификации между клиентом и менеджером очередей IBM MQ необходим «security exit». Вообще термин «exit» в мире IBM обычно означает некий плагин в какой-либо инфраструктуре. В частности, «security exit» это плагин, библиотека, публикующая два метода. Сигнатура методов одинаковая. Один предназначен для реализации NTLM, второй для Kerberos.

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

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

настройка security exit в свойствах канала на менеджере

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

queueManagerProperties.Add(     MQC.TRANSPORT_PROPERTY,      MQC.TRANSPORT_MQSERIES_XACLIENT); //Gives the same result. //queueManagerProperties.Add(MQC.TRANSPORT_PROPERTY, MQC.TRANSPORT_MQSERIES_CLIENT); //Not compatible with native "security exit" plugin implementation. More on that later. //queueManagerProperties.Add(MQC.TRANSPORT_PROPERTY, MQC.TRANSPORT_MQSERIES_MANAGED); queueManagerProperties.Add(     MQC.SECURITY_EXIT_PROPERTY,      "amqrspin(SCY_NTLM)"); queueManagerProperties.Add(     MQC.SECURITY_EXIT_USER_DATA_PROPERTY,      @"domain\client-app-user");

Конечно, «с ходу» ничего не получается. Получаем ошибку подключения типа
2539 (09EB) (RC2539): MQRC_CHANNEL_CONFIG_ERROR
или
2538 (09EA) (RC2538): MQRC_HOST_NOT_AVAILABLE

Исследование журналов на клиенте и на сервере навевает первую дрожь и вселяет первую неуверенность в себе. По умолчанию журналы расположены в папках
%ProgramData%\IBM\MQ\errors
на обеих сторонах. Что же обнаруживается в этих журналах?

AMQ4264.0.FDC
+-----------------------------------------------------------------------------+ |                                                                             | | IBM MQ First Failure Symptom Report                                         | | =========================================                                   | |                                                                             | | Date/Time         :- Thu July 21 2022 15:39:22 UTC                          | | UTC Time          :- 1658417962.217000                                      | | UTC Time Offset   :- 180 ((UNKNOWN))                                        | | Host Name         :- PHD-VS-MQ02                                            | | Operating System  :- Windows Server 2016 Server Datacenter Edition, Build   | |   14393                                                                     | | PIDS              :- 5724H7251                                              | | LVLS              :- 9.1.5.0                                                | | Product Long Name :- IBM MQ for Windows (x64 platform)                      | | Vendor            :- IBM                                                    | | O/S Registered    :- 1 (amqxcs2.dll)                                        | | Data Path         :- C:\ProgramData\IBM\MQ                                  | | Installation Path :- C:\Program Files\IBM\MQ                                | | Installation Name :- Installation1    (1)                                   | | License Type      :- Production                                             | | Probe Id          :- XC130031                                               | | Application Name  :- MQM                                                    | | Component         :- xehExceptionHandler                                    | | SCCS Info         :- F:\build\slot1\p910_P\src\lib\cs\pc\winnt\amqxerrn.c,  | | Line Number       :- 766                                                    | | Build Date        :- Mar 16 2020                                            | | Build Level       :- p915-L200316                                           | | Build Type        :- IKAP - (Production)                                    | | UserID            :- ibmmq                                                  | | Process Path      :- C:\Program Files\IBM\MQ\bin64                          | | Process Name      :- amqrmppa.exe                                           | | Arguments         :- -m QTEST                                               | | Addressing mode   :- 64-bit                                                 | | Process           :- 00004264                                               | | Thread            :- 00000003    RemoteResponder  (6116)                    | | Session           :- 00000000                                               | | UserApp           :- FALSE                                                  | | ConnId(1) IPCC    :- 8924                                                   | | ConnId(3) QM-P    :- 22793                                                  | | Last HQC          :- 1.0.0-1747136                                          | | Last HSHMEMB      :- 0.0.0-0                                                | | Last ObjectName   :-                                                        | | Major Errorcode   :- xecF_E_UNEXPECTED_SYSTEM_RC                            | | Minor Errorcode   :- OK                                                     | | Probe Type        :- MSGAMQ6119                                             | | Probe Severity    :- 1                                                      | | Probe Description :- AMQ6109S: An internal IBM MQ error has occurred.       | | FDCSequenceNumber :- 0                                                      | | Comment1          :- Access Violation at address FFFFFFFFFFFFFFFF when      | |   reading                                                                   | |                                                                             | +-----------------------------------------------------------------------------+ ---> Stack dump for the faulting thread (0x17E4) <--- Stack Backtrace:  # ChildEBP         RetAddr           Param#1          Param#2          Param#3          Param#4           Fn-Loc'n         : Module!Function+Offset [File Name # Line+Offset @ Address] 00 000000A40473E040 00007FFDA37B19A5 (000000A40473E220 0000020A5F376620 00000000C0000100 0000000000000000) 00007FFDA37B345A : amqrspin!terminateSecurityExit+0x9a<NLN:487> 01 000000A40473E0B0 00007FFDA37B15F5 (000000A40473E220 0000020A5F376620 000000A40473E1E0 000000A40473E190) 00007FFDA37B19A5 : amqrspin!SSPI+0x335<NLN:487> 02 000000A40473E100 00007FFD9E3B01C4 (0000020A5F350B60 0000020A659EABC0 0000020A5F376620 00007FFDB825E4FA) 00007FFDA37B15F5 : amqrspin!SCY_NTLM+0xc5<NLN:487> 03 000000A40473E1D0 00007FFD9E3B0DCA (0000020A00000000 0000020A5F376768 0000020A5F376620 0000020A5F376620) 00007FFD9E3B01C4 : amqrdlla!rriTermExit+0x3d4<NLN:487> 04 000000A40473E360 00007FFD9E3E6EF1 (0000020A5F353590 0000020A00000000 0000000000000000 0000020A00000002) 00007FFD9E3B0DCA : amqrdlla!rriTermExits+0x70a<NLN:487> 05 000000A40473EC50 00007FFD9E4B20E7 (00007FFD9E5CF530 000000A40473ED60 000000A40473ECE4 0000020A5F350B60) 00007FFD9E3E6EF1 : amqrdlla!rriFreeSess+0x781<NLN:487> 06 000000A40473EDA0 00007FFD9E4A7F47 (0000020A5F353590 0000020A5F353590 0000020A5F353F40 0000020A5F350B60) 00007FFD9E4B20E7 : amqrdlla!rriAsyncConvControl+0x327<NLN:487> 07 000000A40473EE10 00007FFD9E4B01F2 (0000020A5F379620 0000000000000000 0000020A5F353F40 0000020A5F353590) 00007FFD9E4A7F47 : amqrdlla!cciEndConv+0x287<NLN:487> 08 000000A40473EEC0 00007FFD9E4B0417 (0000020A5F370D60 0000020A5F379620 0000020A5F350B60 00007FFD00000000) 00007FFD9E4B01F2 : amqrdlla!ccxReceiveThreadCleanup+0x1f2<NLN:487> 09 000000A40473EEF0 00007FFD9E3FD4A1 (0000000000000000 000000A40473F000 0000000000000000 0000020A00000000) 00007FFD9E4B0417 : amqrdlla!ccxReceiveThreadFn+0x107<NLN:487> 0A 000000A40473F5E0 00007FFD9E44D0D3 (0000020A5F350B60 000000A400000000 0000020A5F350B60 0000020A5F3536F4) 00007FFD9E3FD4A1 : amqrdlla!rrxResponder+0x1f1<NLN:487> 0B 000000A40473F680 00007FFDAB885CD0 (0000020A00000001 0000020A00000001 0000000000000001 0000020A5F2FDAA0) 00007FFD9E44D0D3 : amqrdlla!cciResponderThread+0x293<NLN:487> 0C 000000A40473F7E0 00007FFDB80BFB80 (0000000000000000 0000000000000000 0000020A5F333EF0 0000000000000000) 00007FFDAB885CD0 : amqxcs2!ThreadMain+0x2c0<NLN:487> 0D 000000A40473F810 00007FFDBAFA84D4 (0000000000000000 0000000000000000 0000000000000000 0000000000000000) 00007FFDB80BFB80 : ucrtbase!o__realloc_base+0x60<NLN:487> 0E 000000A40473F840 00007FFDBBBF1781 (0000000000000000 0000000000000000 0000000000000000 0000000000000000) 00007FFDBAFA84D4 : KERNEL32!BaseThreadInitThunk+0x14<NLN:487> 0F 000000A40473F890 0000000000000000 (0000000000000000 0000000000000000 0000000000000000 0000000000000000) 00007FFDBBBF1781 : ntdll!RtlUserThreadStart+0x21<NLN:487>

ну и ещё много менее вразумительных деталей.

Видно, что функция указанного плагина была вызвана
amqrspin!SCY_NTLM+0xc5NLN:487
и…
Access Violation at address FFFFFFFFFFFFFFFF
приплыли.
В журнале ОС на стороне менеджера IBM MQ видны сообщения о падении процесса, отвечающего за работу канала, amqrmppa.exe

Журнал событий ОС, Application
- <Event xmlns="http://schemas.microsoft.com/win/2004/08/events/event"> - <System>   <Provider Name="Application Error" />    <EventID Qualifiers="0">1000</EventID>    <Level>2</Level>    <Task>100</Task>    <Keywords>0x80000000000000</Keywords>    <TimeCreated SystemTime="2022-08-29T19:25:02.374175800Z" />    <EventRecordID>8583830</EventRecordID>    <Channel>Application</Channel>    <Computer>server.domain.local</Computer>    <Security />    </System> - <EventData>   <Data>amqrmppa.exe</Data>    <Data>9.105.0.20076</Data>    <Data>5e6fa4d9</Data>    <Data>amqrspin.dll</Data>    <Data>9.105.0.20076</Data>    <Data>63060745</Data>    <Data>c0000005</Data>    <Data>00000000000139a6</Data>    <Data>2f4</Data>    <Data>01d8bbdd08c8fef0</Data>    <Data>C:\Program Files\IBM\MQ\bin64\amqrmppa.exe</Data>    <Data>C:\ProgramData\IBM\MQ\exits64\Installation1\amqrspin.dll</Data>    <Data>b10b2211-b70a-4e30-92da-d2489acc1f76</Data>    <Data />    <Data />    </EventData>   </Event>
Faulting application name: amqrmppa.exe, version: 9.105.0.20076, time stamp: 0x5e6fa4d9 Faulting module name: amqrspin.dll, version: 9.105.0.20076, time stamp: 0x63060745 Exception code: 0xc0000005 Fault offset: 0x00000000000139a6 Faulting process id: 0x2f4 Faulting application start time: 0x01d8bbdd08c8fef0 Faulting application path: C:\Program Files\IBM\MQ\bin64\amqrmppa.exe Faulting module path: C:\ProgramData\IBM\MQ\exits64\Installation1\amqrspin.dll Report Id: b10b2211-b70a-4e30-92da-d2489acc1f76 Faulting package full name:  Faulting package-relative application ID: 
Fault bucket , type 0 Event Name: APPCRASH Response: Not available Cab Id: 0  Problem signature: P1: amqrmppa.exe P2: 9.105.0.20076 P3: 5e6fa4d9 P4: amqrspin.dll P5: 9.105.0.20076 P6: 63060745 P7: c0000005 P8: 00000000000139a6 P9:  P10:   Attached files: \\?\C:\Users\ibmmq\AppData\Local\Temp\WER8C45.tmp.appcompat.txt \\?\C:\ProgramData\Microsoft\Windows\WER\Temp\WER8CD3.tmp.WERInternalMetadata.xml C:\ProgramData\Microsoft\Windows\WER\ReportQueue\AppCrash_amqrmppa.exe_ae6ecfd69c49e99d32c25848168b3315947ddce5_92144070_cab_65f98cf0\memory.hdmp C:\ProgramData\Microsoft\Windows\WER\ReportQueue\AppCrash_amqrmppa.exe_ae6ecfd69c49e99d32c25848168b3315947ddce5_92144070_cab_65f98cf0\triagedump.dmp  These files may be available here: C:\ProgramData\Microsoft\Windows\WER\ReportQueue\AppCrash_amqrmppa.exe_ae6ecfd69c49e99d32c25848168b3315947ddce5_92144070_cab_65f98cf0  Analysis symbol:  Rechecking for solution: 0 Report Id: b10b2211-b70a-4e30-92da-d2489acc1f76 Report Status: 4 Hashed bucket: 

Ну что же. По «лёгкой» не получится. Наладил стенд, на обеих сторонах, клиентской и серверной, развернул Visual Studio. Собрал указанный в инструкции исходник amqsspin.c. Запускаю отладку. Проблема воспроизводится относительно легко и стабильно. Живём.

Много часов спустя…наконец обращаю внимание на комментарий в определении структуры

/* definition of the exit user area for the exit */ /* cannot be more than 16 bytes long             */ typedef struct {   MQBYTE                 expectedSecMsg;   PSecurityFunctionTable pSecurityInterface;   HINSTANCE              DllHandle;   PSTATEDATA             pStateData; } SSPIEXITUSERAREA, * PSSPIEXITUSERAREA;

Подождите, у меня же архитектуры всех процессов x64. В них эта структура занимает не положенные 16 байт, а 32! Казалось бы, ну и что?! Откуда такое «ограничение»? С чего это «cannot be more than 16 bytes long». Смотрю, как эта структура используется:

pSecData = (PSSPIEXITUSERAREA)&(pParms->ExitUserArea);

Так так. Что же это за поле такое «pParms->ExitUserArea»?
https://www.ibm.com/docs/en/ibm-mq/9.1?topic=fields-exituserarea-mqbyte16

ExitUserArea (MQBYTE16)

ExitUserArea (MQBYTE16)

Last Updated: 2022-07-24

This field specifies the exit user area — a field available for the exit to use.

It is initialized to binary zero before the first invocation of the exit (which has an ExitReason set to MQXR_INIT), and thereafter any changes made to this field by the exit are preserved across invocations of the exit.

The following value is defined:MQXUA_NONENo user information.

The value is binary zero for the length of the field.

For the C programming language, the constant MQXUA_NONE_ARRAY is also defined; this constant has the same value as MQXUA_NONE, but is an array of characters instead of a string.

The length of this field is given by MQ_EXIT_USER_AREA_LENGTH. This is an input/output field to the exit.

Диалог, я полагаю, состоялся примерно такой между программистами IT гиганта:

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

Что же сделали эти бандиты? Написали комментарий:

/* definition of the exit user area for the exit */ /* cannot be more than 16 bytes long             */

Вы можете возразить: «это же просто пример!». Ну да, пример. А компонент amqrspin.dll, поставляемый с продуктом, тоже падает для примера?! Менеджер IBM MQ давно поставляется в варианте x64 и вместе с ним поставляется amqrspin.dll, собранная для x64.

Корректировка проста. Выделяю память для контекста, сохраняю адрес его указателя в отведённых плагину данных:

PSSPIEXITUSERAREA pSecData = malloc(sizeof(SSPIEXITUSERAREA)); memset(pSecData, 0, sizeof(SSPIEXITUSERAREA)); memcpy(&(pParms->ExitUserArea), &pSecData, sizeof(void*));

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

free(pSecData); memset(pSecData, 0, MQ_EXIT_USER_AREA_LENGTH);

Этого достаточно для NTLM.

Однако, чтобы заработал Kerberos предстоит ещё разобраться с Service Principal Name и некорректным значением константы MQC.SECURITY_EXIT_USER_DATA_PROPERTY, которую я использовал для настройки клиентского подключения, на корректное значение, которое обнаруживается в исходнике amqsspin.c

queueManagerProperties.Add(     "securityUserData",      @"domain\client-app-user");

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

ibmMQSeries/<queue manager name>

для моего случая:

ibmMQSeries/QTEST

Этого достаточно для Kerberos.

На сладкое

Во сторой части будут представлены результаты изысканий портирования amqsspin.c на .NET для сопряжения IWA и распределенных транзакций под координацией MS DTC. Ну и полные исходники примеров с пошаговой инструкцией.


ссылка на оригинал статьи https://habr.com/ru/post/686490/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *