Удалённое выполнение кода в InterSystems Caché (RCE)

Van Gogh Fishing Boats on the Beach

Введение


В том случае, если вы управляете более чем одним сервером Caché может возникнуть задача выполнения произвольного кода из одного сервера Caché на другом. Кроме того, может потребоваться выполнение произвольного кода на удалённом сервере Caché, например, для нужд сисадмина… Для решения этих задач была разработана утилита RCE.

Какие вообще есть варианты решения подобных задач, и что предлагает RCE (Remote Code Execution) – под катом.

Что уже есть?


» Локальные команды ОС


Начнём с простого – выполнения локальных команд операционной системы из Caché. Для этого используются функции $zf:
Также можно использовать методы класса %Net.Remote.Utility, которые предоставляют собой удобные обёртки над стандартными функциями, их преимуществом является получение вывода вызванных процессов в более удобной форме:

  • RunCommandViaCPIPE – выполняет команду через Command Pipe. Возвращает созданное устройство и строку с выводом процесса. Напрямую выполнение команд на сервере с помощью Command Pipe описано на Хабре в этой статье.
  • RunCommandViaZF – выполняет команду через $ZF(-1). Записывает вывод процесса в файл, а также возвращает его в виде строки.


Альтернативным вариантом является использование терминальной команды ! (или $, они идентичны), которая открывает оболочку операционной системы внутри терминала Caché. Есть два режима работы:

  • Однострочный – вместе с ! передаётся сама команда. Она сразу выполняется интерпретатором оболочки, а её вывод отправляется на текущее устройство Caché. Предыдущий пример выглядит так:
    SAMPLES>! mkdir ""test folder""
    

  • Многострочный – сначала выполняется !, что приводит к открытию оболочки, в которую пользователь уже вводит команды операционной системы. Выход осуществляется с помощью команд quit или exit (в зависимости от оболочки):
    SAMPLES>! 
    C:\InterSystems\Cache\mgr\samples\> mkdir "test folder"
    C:\InterSystems\Cache\mgr\samples\> quit
    SAMPLES>
    


» Удалённое выполнение COS кода


Возможно с помощью класса %Net.RemoteConnection, где доступна следующая функциональность:

  • Открытие и изменение хранимых объектов;
  • Выполнение методов класса и объекта;
  • Выполнение запросов.


Пример кода, демонстрирующего эти возможности

    Set rc=##class(%Net.RemoteConnection).%New()
    
Set Status=rc.Connect("127.0.0.1","SAMPLES",1972,"_system","SYS"break:'Status
    
Set Status=rc.OpenObjectId("Sample.Person",1,.perbreak:'Status
    
Set Status=rc.GetProperty(per,"Name",.valuebreak:'Status
    
Write value
    
Set Status=rc.ResetArguments() break:'Status
    
Set Status=rc.SetProperty(per,"Name","Jones,Tom "_$r(100),4) break:'Status 
    
Set Status=rc.ResetArguments() break:'Status
    
Set Status=rc.GetProperty(per,"Name",.valuebreak:'Status
    
Write value
    
Set Status=rc.ResetArguments() break:'Status
    
Set Status=rc.AddArgument(150,0) break:'Status  // Сложение 150+10
    
Set Status=rc.AddArgument(10,0) break:'Status  // Сложение 150+10
    
Set Status=rc.InvokeInstanceMethod(per"Addition", .AdditionValue, 1) break:'Status
    
Write AdditionValue
    
Set Status=rc.ResetArguments() break:'Status
    
Set Status=rc.InstantiateQuery(.rs,"Sample.Person","ByName")


В данном коде происходит:

  • Подключение к серверу Caché;
  • Открытие экземпляра класса Sample.Person с Id 1;
  • Получение значения свойства;
  • Изменение значения свойства;
  • Установка аргументов для метода;
  • Вызов метода экземпляра;
  • Выполнение запроса Sample.Person:ByName.


Для работы %Net.RemoteConnection на стороне сервера, отправляющего запросы необходима настройка C++ биндинга.

Отдельно следует упомянуть о технологии ECP, о которой писали на Хабре, и которая позволяет вызывать удаленные JOB процессы со стороны сервера приложений на сервере БД.

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

RCE


Таким образом, перед проектом были поставлены следующие цели:

  • Выполнение скриптов на удалённых серверах из Caché;
  • Отсутствие необходимости настройки удалённого сервера (далее – клиента);
  • Минимальная настройка локального сервера (далее – сервера);
  • Прозрачное для пользователя переключение между командами операционной системы и COS;
  • Поддержка Windows и Linux в качестве клиента.


Иерархия классов проекта выглядит следующим образом:

Иерархия классов проекта RCE


Иерархия Машина – ОС – Инстанс служит для хранения информации, необходимой для доступа к удалённым серверам.
Для хранения команд служит класс RCE.Script, который содержит последовательный список – объектов класса RCE.Command, которые могут быть как командами ОС так и COS кодом.

Пример команд:

    Set Сommand1 ##class(RCE.Command).%New("cd 1",0)
    
Set Сommand2 ##class(RCE.Command).%New("zn ""%SYS""",1)


Первый аргумент – текст команды, второй – уровень выполнения: 0 – ОС, 1 – Cache.

Пример создания нового скрипта:

    Set Script ##class(RCE.Script).%New()
    
Do Script.Insert(##class(RCE.Command).%New("touch 123",0))
    
Do Script.Insert(##class(RCE.Command).%New("set ^test=1",1))
    
Do Script.Insert(##class(RCE.Command).%New("set ^test(1)=2",1))
    
Do Script.Insert(##class(RCE.Command).%New("touch 1234",0))
    
Do Script.%Save()


Здесь на уровне ОС будут выполнены 1-я и 4-я команда, а 2-я и 3-я будут выполнены в Caché, причём процесс переключения уровня выполнения абсолютно прозрачен для пользователя.

» Механизмы выполнения


Сейчас поддерживаются следующие пути исполнения:

Сервер
Клиент
Linux
Linux, Windows (требуется установка SSH сервера на клиенте)
Windows
Linux, Windows (требуется установка SSH сервера на клиенте или psexec на сервере)

В том случае, если есть поддержка ssh на клиенте, сервер генерирует ssh команду и выполняет её на клиенте с помощью стандартного класса %Net.SSH.Session.

В случае же, если и сервер и клиент – под управлением ОС Windows, происходит генерация bat-файла, который потом отправляется на клиент и выполняется с помощью утилиты psexec.

» Добавление сервера


Загрузите классы из репозитория в любую область. В случае, если у вас Windows сервер и вы хотите управлять другими Windows серверами, установите значение глобала ^settings(«exec») равное пути к утилите psexec. На этом настройка завершена!

» Добавление клиента


Состоит в сохранении всех необходимых для аутентификации данных.

Пример кода, создающего новую иерархию Машина – ОС – Инстанс

    Set Machine ##class(RCE.Machine).%New()
    
Set Machine.IP "IP или Хост"
    
    
Set OS ##class(RCE.OS).%New("OС"// Linux или Windows
    
Set OS.Username "Имя пользователя ОС"
    
Set OS.Password "Пароль пользователя"
    
Set Instance ##class(RCE.Instance).%New()
    
Set Instance.Name "Имя инстанса Caché" 
    
Set Instance.User "Имя пользователя Caché"    // Не надо, если установлены минимальные настройки безопасности 
    
Set Instance.Pass "Пароль пользователя Caché" // Не надо, если установлены минимальные настройки безопасности 
    
Set Instance.Dir "Путь к cterm инстанса"      // Надо, только если cterm не в PATH (для windows клиентов)
    
    
Set Instance.OS OS
    
Set OS.Machine Machine
    
Write $System.Status.GetErrorText(Machine.%Save())


» Выполнение скрипта


Продолжая предыдущие примеры, выполнение скрипта происходит очень просто – с помощью метода ExecuteScript класса RCE.Instance, которому передаются объект скрипта и область выполнения (по умолчанию – %SYS):

    Set Status Instance.ExecuteScript(Script,"USER")


Выводы


RCE предоставляет удобный механизм удалённого выполнения кода из InterSystems Caché. Так как скрипты хранимые, вам необходимо написать скрипт только один раз, потом он может выполнятся когда угодно и на любом числе клиентов.

Ссылки


GitHub репозиторий RCE
Архив классов проекта RCE

© Habrahabr.ru