n8n-nodes-binalyze-air
Version:
Binalyze AIR nodes for automating DFIR with n8n workflows
643 lines • 24.4 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.InterACTOperations = void 0;
exports.executeInterACT = executeInterACT;
exports.getCasesByOrganization = getCasesByOrganization;
exports.getAssetsByOrganizationForInteract = getAssetsByOrganizationForInteract;
const n8n_workflow_1 = require("n8n-workflow");
const helpers_1 = require("../utils/helpers");
const interact_1 = require("../api/interact/interact");
const tasks_1 = require("../api/tasks/tasks");
const organizations_1 = require("./organizations");
const cases_1 = require("../api/cases/cases");
const assets_1 = require("../api/assets/assets");
function isValidCase(entity) {
return entity && entity._id && typeof entity._id === 'string';
}
function extractCaseId(entity) {
if (!entity)
return '';
return entity._id || '';
}
function isValidAsset(entity) {
return entity && entity._id && typeof entity._id === 'string';
}
function extractAssetId(entity) {
if (!entity)
return '';
return entity._id || '';
}
exports.InterACTOperations = [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
displayOptions: {
show: {
resource: ['interact'],
},
},
options: [
{
name: 'Close Session',
value: 'closeSession',
description: 'Close an InterACT session',
action: 'Close a session',
},
{
name: 'Create InterACT Session',
value: 'createSession',
description: 'Create a new InterACT shell session for an asset',
action: 'Create an interact session',
},
{
name: 'Execute Async Command',
value: 'executeAsyncCommand',
description: 'Execute an asynchronous command in an InterACT session',
action: 'Execute an async command',
},
{
name: 'Execute Command',
value: 'executeCommand',
description: 'Execute a command in an InterACT session',
action: 'Execute a command',
},
{
name: 'Get Command Response',
value: 'getCommandMessage',
description: 'Get the result of a command execution',
action: 'Get a command response',
},
{
name: 'Interrupt Command',
value: 'interruptCommand',
description: 'Interrupt a running command',
action: 'Interrupt a command',
},
{
name: 'Wait for Session to Be Live',
value: 'waitForSession',
description: 'Wait for an InterACT session to become live by monitoring task status',
action: 'Wait for session to be live',
},
],
default: 'createSession',
},
{
displayName: 'Organization',
name: 'organizationId',
type: 'resourceLocator',
default: { mode: 'list', value: '' },
placeholder: 'Select an organization...',
displayOptions: {
show: {
resource: ['interact'],
operation: ['createSession'],
},
},
modes: [
{
displayName: 'From List',
name: 'list',
type: 'list',
placeholder: 'Select an organization...',
typeOptions: {
searchListMethod: 'getOrganizations',
searchable: true,
},
},
{
displayName: 'By ID',
name: 'id',
type: 'string',
validation: [
{
type: 'regex',
properties: {
regex: '^[0-9]+$',
errorMessage: 'Not a valid organization ID (must be a positive number)',
},
},
],
placeholder: 'Enter Organization ID',
},
{
displayName: 'By Name',
name: 'name',
type: 'string',
placeholder: 'Enter organization name',
},
],
required: true,
description: 'The organization that the asset belongs to',
},
{
displayName: 'Case',
name: 'caseId',
type: 'resourceLocator',
default: { mode: 'list', value: '' },
placeholder: 'Select a case...',
displayOptions: {
show: {
resource: ['interact'],
operation: ['createSession'],
},
},
modes: [
{
displayName: 'From List',
name: 'list',
type: 'list',
placeholder: 'Select a case...',
typeOptions: {
searchListMethod: 'getCasesByOrganization',
searchable: true,
},
},
{
displayName: 'By ID',
name: 'id',
type: 'string',
validation: [
{
type: 'regex',
properties: {
regex: '^[a-zA-Z0-9-_]+$',
errorMessage: 'Not a valid case ID (must contain only letters, numbers, hyphens, and underscores)',
},
},
],
placeholder: 'Enter case ID',
},
],
description: 'The case to associate with the InterACT session (optional)',
},
{
displayName: 'Asset',
name: 'assetId',
type: 'resourceLocator',
default: { mode: 'list', value: '' },
placeholder: 'Select an asset...',
displayOptions: {
show: {
resource: ['interact'],
operation: ['createSession'],
},
},
modes: [
{
displayName: 'From List',
name: 'list',
type: 'list',
placeholder: 'Select an asset...',
typeOptions: {
searchListMethod: 'getAssetsByOrganizationForInteract',
searchable: true,
},
},
{
displayName: 'By ID',
name: 'id',
type: 'string',
validation: [
{
type: 'regex',
properties: {
regex: '^[a-zA-Z0-9-_]+$',
errorMessage: 'Not a valid asset ID (must contain only letters, numbers, hyphens, and underscores)',
},
},
],
placeholder: 'Enter asset ID',
},
],
required: true,
description: 'The asset to create the InterACT session for',
},
{
displayName: 'Session ID',
name: 'sessionId',
type: 'string',
default: '',
placeholder: 'Enter session ID',
displayOptions: {
show: {
resource: ['interact'],
operation: ['executeCommand', 'executeAsyncCommand', 'closeSession', 'getCommandMessage', 'interruptCommand', 'waitForSession'],
},
},
required: true,
description: 'The InterACT session ID',
},
{
displayName: 'Task ID',
name: 'taskId',
type: 'string',
default: '',
placeholder: 'Enter task ID',
displayOptions: {
show: {
resource: ['interact'],
operation: ['waitForSession'],
},
},
required: true,
description: 'The InterACT session task ID to monitor for status changes',
},
{
displayName: 'Message ID',
name: 'messageId',
type: 'string',
default: '',
placeholder: 'Enter message ID',
displayOptions: {
show: {
resource: ['interact'],
operation: ['getCommandMessage', 'interruptCommand'],
},
},
required: true,
description: 'The command message ID',
},
{
displayName: 'Command',
name: 'command',
type: 'string',
default: '',
placeholder: 'Enter command to execute',
displayOptions: {
show: {
resource: ['interact'],
operation: ['executeCommand', 'executeAsyncCommand'],
},
},
required: true,
description: 'The command to execute in the InterACT session',
},
{
displayName: 'Response Type',
name: 'responseType',
type: 'options',
default: 'json',
displayOptions: {
show: {
resource: ['interact'],
operation: ['executeCommand', 'executeAsyncCommand'],
},
},
options: [
{
name: 'JSON',
value: 'json',
description: 'Return response as JSON',
},
{
name: 'Text',
value: 'text',
description: 'Return response as plain text',
},
],
description: 'The format of the command response',
},
{
displayName: 'Timeout (Seconds)',
name: 'timeout',
type: 'number',
default: 60,
placeholder: 'Enter timeout in seconds',
displayOptions: {
show: {
resource: ['interact'],
operation: ['waitForSession'],
},
},
required: true,
description: 'Maximum time to wait for the session to become live (in seconds). Set to 0 to wait indefinitely.',
typeOptions: {
minValue: 0,
},
},
];
async function executeInterACT() {
const items = this.getInputData();
const returnData = [];
const operation = this.getNodeParameter('operation', 0);
try {
const credentials = await (0, helpers_1.getAirCredentials)(this);
for (let i = 0; i < items.length; i++) {
try {
let responseData;
switch (operation) {
case 'createSession':
responseData = await handleCreateInterACTSession(this, credentials, i);
break;
case 'executeCommand':
responseData = await handleExecuteCommand(this, credentials, i);
break;
case 'executeAsyncCommand':
responseData = await handleExecuteAsyncCommand(this, credentials, i);
break;
case 'interruptCommand':
responseData = await handleInterruptCommand(this, credentials, i);
break;
case 'closeSession':
responseData = await handleCloseSession(this, credentials, i);
break;
case 'getCommandMessage':
responseData = await handleGetCommandMessage(this, credentials, i);
break;
case 'waitForSession':
responseData = await handleWaitForSession(this, credentials, i);
break;
default:
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `The operation '${operation}' is not supported`);
}
const executionData = this.helpers.constructExecutionMetaData(this.helpers.returnJsonArray(responseData), { itemData: { item: i } });
returnData.push(...executionData);
}
catch (error) {
(0, helpers_1.handleExecuteError)(this, error, i, returnData);
}
}
}
catch (error) {
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Failed to execute InterACT operation: ${error instanceof Error ? error.message : String(error)}`);
}
return [returnData];
}
async function handleCreateInterACTSession(context, credentials, itemIndex) {
const organizationIdParam = context.getNodeParameter('organizationId', itemIndex);
const caseIdParam = context.getNodeParameter('caseId', itemIndex, '');
const assetIdParam = context.getNodeParameter('assetId', itemIndex);
if (typeof organizationIdParam === 'object' && organizationIdParam.mode === 'name') {
const orgResult = await (0, organizations_1.findOrganizationByName)(context, credentials, organizationIdParam.value);
if (!orgResult) {
throw new n8n_workflow_1.NodeOperationError(context.getNode(), 'Organization not found');
}
}
let caseId = null;
if (caseIdParam) {
if (typeof caseIdParam === 'object' && caseIdParam.value) {
caseId = caseIdParam.value;
}
else if (typeof caseIdParam === 'string' && caseIdParam) {
caseId = caseIdParam;
}
}
let assetId;
if (typeof assetIdParam === 'object' && assetIdParam.value) {
assetId = assetIdParam.value;
}
else if (typeof assetIdParam === 'string') {
assetId = assetIdParam;
}
else {
throw new n8n_workflow_1.NodeOperationError(context.getNode(), 'Asset ID is required');
}
const data = {
assetId: assetId,
caseId: caseId,
taskConfig: {
choice: 'use-policy'
}
};
const response = await interact_1.api.createInterACTSession(context, credentials, data);
if (response.result && response.result.data) {
const result = response.result;
const baseUrl = credentials.instanceUrl.replace(/\/$/, '');
return {
sessionId: result.data.sessionId,
assetId: result.assetId || assetId,
taskId: result.id,
type: result.type,
loginUrl: baseUrl + result.loginUrl,
shellUrl: baseUrl + result.shellUrl,
reportUrl: baseUrl + result.reportUrl
};
}
else {
throw new n8n_workflow_1.NodeOperationError(context.getNode(), 'Failed to create InterACT session');
}
}
async function handleExecuteCommand(context, credentials, itemIndex) {
const sessionId = context.getNodeParameter('sessionId', itemIndex);
const command = context.getNodeParameter('command', itemIndex);
const responseType = context.getNodeParameter('responseType', itemIndex);
const data = {
command,
accept: responseType,
};
const response = await interact_1.api.executeCommand(context, credentials, sessionId, data);
return response.result;
}
async function handleExecuteAsyncCommand(context, credentials, itemIndex) {
const sessionId = context.getNodeParameter('sessionId', itemIndex);
const command = context.getNodeParameter('command', itemIndex);
const responseType = context.getNodeParameter('responseType', itemIndex);
const data = {
command,
accept: responseType,
};
const response = await interact_1.api.executeAsyncCommand(context, credentials, sessionId, data);
return response.result;
}
async function handleInterruptCommand(context, credentials, itemIndex) {
const sessionId = context.getNodeParameter('sessionId', itemIndex);
const messageId = context.getNodeParameter('messageId', itemIndex);
const response = await interact_1.api.interruptCommand(context, credentials, sessionId, messageId);
if (response.success && response.result === null) {
return {
success: true,
messageId: messageId,
sessionId: sessionId,
status: 'interrupted',
message: 'Command interrupted successfully'
};
}
return response.result || {
success: response.success,
messageId: messageId,
sessionId: sessionId,
status: 'interrupted',
message: 'Command interrupted'
};
}
async function handleCloseSession(context, credentials, itemIndex) {
const sessionId = context.getNodeParameter('sessionId', itemIndex);
const response = await interact_1.api.closeSession(context, credentials, sessionId);
return response.result;
}
async function handleGetCommandMessage(context, credentials, itemIndex) {
const sessionId = context.getNodeParameter('sessionId', itemIndex);
const messageId = context.getNodeParameter('messageId', itemIndex);
const response = await interact_1.api.getCommandMessage(context, credentials, sessionId, messageId);
return response.result;
}
async function handleWaitForSession(context, credentials, itemIndex) {
const sessionId = context.getNodeParameter('sessionId', itemIndex);
const taskId = context.getNodeParameter('taskId', itemIndex);
const timeout = context.getNodeParameter('timeout', itemIndex);
const startTime = Date.now();
const pollInterval = 60000;
while (true) {
try {
const taskResponse = await tasks_1.api.getTaskById(context, credentials, taskId);
const task = taskResponse.result;
if (task && task.type === 'interact-shell') {
if (task.status === 'processing') {
return {
sessionId: sessionId,
taskId: taskId,
status: 'live',
message: 'InterACT session is ready - task is processing',
taskStatus: task.status,
taskType: task.type,
organizationId: task.organizationId,
createdAt: task.createdAt,
updatedAt: task.updatedAt
};
}
else if (task.status === 'completed') {
return {
sessionId: sessionId,
taskId: taskId,
status: 'completed',
message: 'InterACT session task completed',
taskStatus: task.status,
taskType: task.type,
organizationId: task.organizationId,
createdAt: task.createdAt,
updatedAt: task.updatedAt
};
}
else if (task.status === 'cancelled') {
return {
sessionId: sessionId,
taskId: taskId,
status: 'cancelled',
message: 'InterACT session task was cancelled',
error: 'Task was cancelled before becoming live',
taskStatus: task.status,
taskType: task.type,
organizationId: task.organizationId,
createdAt: task.createdAt,
updatedAt: task.updatedAt
};
}
else if (task.status === 'failed') {
return {
sessionId: sessionId,
taskId: taskId,
status: 'failed',
message: 'InterACT session task failed',
error: 'Task failed during execution',
taskStatus: task.status,
taskType: task.type,
organizationId: task.organizationId,
createdAt: task.createdAt,
updatedAt: task.updatedAt
};
}
}
else {
throw new n8n_workflow_1.NodeOperationError(context.getNode(), `Invalid task: Task ID ${taskId} is not an InterACT session task or was not found`);
}
}
catch (error) {
if (error instanceof n8n_workflow_1.NodeOperationError) {
throw error;
}
console.log(`Task ${taskId} status check failed: ${error instanceof Error ? error.message : String(error)}`);
}
if (timeout > 0) {
const elapsedTime = (Date.now() - startTime) / 1000;
if (elapsedTime >= timeout) {
return {
sessionId: sessionId,
taskId: taskId,
status: 'timeout',
message: `InterACT session did not become live within ${timeout} seconds`,
error: `Timeout exceeded. Task is still waiting to become live`,
taskStatus: 'unknown'
};
}
}
await new Promise(resolve => setTimeout(resolve, pollInterval));
}
}
async function getCasesByOrganization(searchTerm) {
var _a;
try {
const credentials = await (0, helpers_1.getAirCredentials)(this);
let organizationId = '0';
try {
const currentParams = this.getCurrentNodeParameters();
if (currentParams && currentParams.organizationId) {
const orgResource = currentParams.organizationId;
if (typeof orgResource === 'object') {
if (orgResource.mode === 'id' || orgResource.mode === 'list') {
organizationId = orgResource.value || '0';
}
}
else if (typeof orgResource === 'string') {
organizationId = orgResource;
}
}
}
catch (error) {
}
const additionalParams = {};
if (searchTerm) {
additionalParams.searchTerm = searchTerm;
}
const response = await cases_1.api.getCases(this, credentials, organizationId, additionalParams);
const cases = ((_a = response.result) === null || _a === void 0 ? void 0 : _a.entities) || [];
return (0, helpers_1.createListSearchResults)(cases, isValidCase, (caseItem) => ({
name: caseItem.name || `Case ${caseItem._id}`,
value: extractCaseId(caseItem),
}), searchTerm);
}
catch (error) {
throw (0, helpers_1.catchAndFormatError)(error, 'loading cases');
}
}
async function getAssetsByOrganizationForInteract(searchTerm) {
var _a;
try {
const credentials = await (0, helpers_1.getAirCredentials)(this);
let organizationId = '0';
try {
const currentParams = this.getCurrentNodeParameters();
if (currentParams && currentParams.organizationId) {
const orgResource = currentParams.organizationId;
if (typeof orgResource === 'object') {
if (orgResource.mode === 'id' || orgResource.mode === 'list') {
organizationId = orgResource.value || '0';
}
}
else if (typeof orgResource === 'string') {
organizationId = orgResource;
}
}
}
catch (error) {
}
const queryParams = {
'filter[managedStatus]': 'managed',
};
if (searchTerm) {
queryParams['filter[searchTerm]'] = searchTerm;
}
const response = await assets_1.api.getAssets(this, credentials, organizationId, queryParams);
const assets = ((_a = response.result) === null || _a === void 0 ? void 0 : _a.entities) || [];
return (0, helpers_1.createListSearchResults)(assets, isValidAsset, (asset) => ({
name: `${asset.name} (${asset.onlineStatus || 'Unknown'} - ${asset.ipAddress || 'No IP'})`,
value: extractAssetId(asset),
}), searchTerm);
}
catch (error) {
throw (0, helpers_1.catchAndFormatError)(error, 'loading assets');
}
}
//# sourceMappingURL=interact.js.map