После формирования структуры проекта можно перейти к наполнению логикой операции выполнения загруженных пользователем скриптов.
В данном разделе описана логика выполнения PowerShell скрипта в рамках этой системной операции.
Данная операция отвечает за выполнение загруженных пользователем скриптов. Подключение к удаленной машине происходит по протоколу WMI. Как результат на этом хосте выполняется команда для выполнения скрипта.
Для реализации операции RPC будет иметь следующую сигнатуру:
rpc InvokeScript(InvokeScriptWindowsRequest) returns (InvokeScriptWindowsResponse)
На вход модуль получает следующий запрос в формате Protocol buffers:
Тип InvokeScriptWindowsRequest:
device:- Тип параметра:
DeviceContent - Описание: Данные об устройстве.
- Тип параметра:
script_url:- Тип параметра:
string - Описание: Ссылка на скрипт.
- Тип параметра:
invoke_mode:- Тип параметра:
WindowsInvokeMode - Описание: Режим выполнения скрипта.
- Тип параметра:
Тип DeviceContent:
device_id:- Тип параметра:
string - Описание: Идентификатор устройства.
- Тип параметра:
model_name:- Тип параметра:
string - Описание: Модель устройства.
- Тип параметра:
vendor_name:- Тип параметра:
string - Описание: Вендор устройства.
- Тип параметра:
connectors:- Тип параметра:
repeated DeviceConnector - Описание: Список интерфейсов подключения к устройству.
- Тип параметра:
Тип DeviceConnector:
device_network_id:- Тип параметра:
string - Описание: Идентификатор сетевого интерфейса устройства.
- Тип параметра:
address:- Тип параметра:
string - Описание: Адрес подключения (ip/fqdn).
- Тип параметра:
mac:- Тип параметра:
string - Описание: MAC-адрес устройства.
- Тип параметра:
credentials:- Тип параметра:
repeated Credential - Описание: Список данных подключения к устройству.
- Тип параметра:
Тип Credential:
protocol:- Тип параметра:
ConnectorProtocol - Описание: Протокол подключения.
- Тип параметра:
login:- Тип параметра:
string - Описание: Логин для подключения.
- Тип параметра:
password:- Тип параметра:
string - Описание: Пароль для подключения.
- Тип параметра:
port:- Тип параметра:
int32 - Описание: Порт подключения.
- Тип параметра:
cipher:- Тип параметра:
int32 - Описание: Шифрование (только для IPMI).
- Тип параметра:
version:- Тип параметра:
int32 - Описание: Версия протокола (только для SNMP).
- Тип параметра:
community:- Тип параметра:
string - Описание: Community слово (только для SNMP).
- Тип параметра:
security_name:- Тип параметра:
string - Описание: Security name (только для SNMP).
- Тип параметра:
context:- Тип параметра:
string - Описание: Контекст подключения (только для SNMP).
- Тип параметра:
auth_protocol:- Тип параметра:
string - Описание: Auth protocol (только для SNMP).
- Тип параметра:
auth_key:- Тип параметра:
string - Описание: Auth key (только для SNMP).
- Тип параметра:
private_protocol:- Тип параметра:
string - Описание: Private protocol (только для SNMP).
- Тип параметра:
private_key:- Тип параметра:
string - Описание: Private key (только для SNMP).
- Тип параметра:
security_level:- Тип параметра:
string - Описание: Уровень безопасности.
- Тип параметра:
Перечисление WindowsInvokeMode:
WINDOWS_INVOKE_MODE_UNSPECIFIED:- Описание: Невалидное значение.
WINDOWS_INVOKE_MODE_STANDARD:- Описание: Стандартный режим запуска скрипта.
WINDOWS_INVOKE_MODE_ADMIN:- Описание: Запуск скрипта с правами администратора.
Структура Credential является общей для реализации операции выполнения загруженных пользователем скриптов по разным протоколам, поэтому может содержать большее количество полей, чем поддерживает модуль расширения.
Для корректной работы сбора данных, устройство должно иметь хотя бы одно действительное подключение с протоколом WMI (поле protocol списка credentials со значением CONNECTOR_PROTOCOL_WMI), остальные устройства модуль должен игнорировать.
Пример данных запроса:
{
"device": {
"device_id": "test",
"connectors": [
{
"address": "10.1.18.34",
"credentials": [
{
"login": "Administrator",
"password": "1qaz@WSX",
"port": 5985,
"protocol": "CONNECTOR_PROTOCOL_WMI"
}
]
}
]
},
"invoke_mode": "WINDOWS_INVOKE_MODE_STANDARD",
"script_url": "
}В качестве cообщения-ответа используется модель:
Тип InvokeScriptWindowsResponse:
result:- Тип параметра:
OperationResult - Описание: Результат выполнения скрипта.
- Тип параметра:
Тип OperationResult:
device_id:- Тип параметра:
string - Описание: Идентификатор устройства, на котором происходила операция.
- Тип параметра:
state:- Тип параметра:
OperationState - Описание: Тип результата выполнения операции.
- Тип параметра:
output:- Тип параметра:
string - Описание: Текстовое описание результата выполнения операции.
- Тип параметра:
Перечисление OperationState:
OPERATION_STATE_UNSPECIFIED:- Описание: Невалидное значение.
OPERATION_STATE_SUCCESS:- Описание: Операция завершена успешно.
OPERATION_STATE_FAILED:- Описание: Выполнение операции завершилось с ошибкой.
С полной структурой данных вы можете ознакомиться в протофайлах.
Так как сообщения для операций идут по Protocol Buffer, необходимо иметь возможность скомпилировать protobuf-файлы под конкретный язык программирования.
Подробнее об этом:
Реализация операции будет производиться на виртуальной машине, запущенной c помощью программы по виртуализации Proxmox со следующими характеристикам:
- Название ОС:
Microsoft Windows Server 2019 Standard - Версия:
10.0.17763 Build 17763 - Процессор:
Common KVM processor, 2095 Mhz, 2 Core(s), 2 Logical Processor(s) - Объем памяти дисков:
100,00 GB - Суммарно установлено памяти (ОЗУ):
4,00 GB - Количество плашек ОЗУ:
1
Для реализации системной операции необходимо иметь возможность подключения к удаленному хосту по протоколу WinRM, для этого необходимо настроить подключение на удаленной машине.
Имея возможность подключения к удаленной машине с ОС Windows мы можем отправлять консольные команды на запуск скриптов Powershell.
Подробнее по ссылке:
Для корректного выполнения скрипта в PowerShell перед содержимым скрипта добавляется строка $ProgressPreference = 'SilentlyContinue';, после чего она кодируется в формат UTF16-LE.
Результат кодирования приписывается в конце строки powershell.exe -EncodedCommand. Это и будет являться целевой командой выполнения скрипта.
Подготовка PowerShell команды:
// Powershell wraps a PowerShell script
// and prepares it for execution by the winrm client
// Powershell wraps a PowerShell script
// and prepares it for execution by the winrm client
func Powershell(psCmd string) string {
// Disable unnecessary progress bars which considered as stderr.
psCmd = "$ProgressPreference = 'SilentlyContinue';" + psCmd
// Encode string to UTF16-LE
encoder := unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM).NewEncoder()
encoded, err := encoder.String(psCmd)
if err != nil {
return ""
}
// Finally make it base64 encoded which is required for powershell.
psCmd = base64.StdEncoding.EncodeToString([]byte(encoded))
// Specify powershell.exe to run encoded command
return "powershell.exe -EncodedCommand " + psCmd
}Запрос к удаленному устройству по WMI выглядит следующим образом:
func SendWinRMCommand(
ctx context.Context,
ip, login, pass string,
port int,
cmd string,
) (string, error) {
client, err := newWinRMClient(ip, login, pass, port)
if err != nil {
return "", fmt.Errorf("create WMI client error: %s", err)
}
var stdout, stderr bytes.Buffer
_, err = client.RunWithContext(ctx, "chcp 866 | "+cmd, &stdout, &stderr)
if err != nil {
return "", fmt.Errorf("cmd [%s] WinRM error: %s", cmd, err)
}
stderrStr := stderr.String()
stdoutStr := stdout.String()
if stderrStr != "" {
return "", fmt.Errorf("cmd [%s] error: %s", cmd, stderrStr)
}
reader := transform.NewReader(bytes.NewReader([]byte(stdoutStr)), charmap.CodePage866.NewDecoder())
d, err := io.ReadAll(reader)
if err != nil {
return "", fmt.Errorf("encoding stdout [%s] error [%s]", stdoutStr, err)
}
return string(d), nil
}Пример готового проекта расположен в папке project