Задача
В одном из проектов пришлось подойти к незаслуженно заброшенному и игнорируемому снаряду. Среда функционирования:
-
Активная директория 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/
Добавить комментарий