Задача

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

  • Активная директория 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. Ну и полные исходники примеров с пошаговой инструкцией.

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


  1. Glob666
    05.09.2022 17:41
    +1

    Вот понаберут же индусских программистов!


    1. lirco Автор
      05.09.2022 17:42

      Это вы ещё в ракете не видели исходников клиентской библиотеки для .NET не видели


      1. Glob666
        05.09.2022 17:45

        К сожалению в стандартной поставке MQ исходники с аутентификацией - только под обычный Си.


        1. lirco Автор
          05.09.2022 17:46

          Как раз этому и будет посвящена вторая часть. Будут исходники для .NET!