UNPKG

@mdf.js/service-registry

Version:

MMS - API - Service Registry

244 lines 10.5 kB
"use strict"; /** * Copyright 2024 Mytra Control S.L. All rights reserved. * * Use of this source code is governed by an MIT-style license that can be found in the LICENSE file * or at https://opensource.org/licenses/MIT. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.ControlManager = void 0; const tslib_1 = require("tslib"); const crash_1 = require("@mdf.js/crash"); const openc2_1 = require("@mdf.js/openc2"); const cluster_1 = tslib_1.__importDefault(require("cluster")); const lodash_1 = require("lodash"); const stream_1 = require("stream"); /** * Customizer function used for merging objects with `MergeWith` function. * @param objValue - The value from the destination object. * @param srcValue - The value from the source object. * @returns The merged value or `undefined` if no merge is needed. */ const customizer = (objValue, srcValue) => { if (Array.isArray(objValue)) { return Array.from(new Set(objValue.concat(srcValue)).values()); } return undefined; }; /** * ControlManager handles OpenC2 command and control interactions, serving as the bridge between * OpenC2 Consumers and the Service. * It extends EventEmitter to re-emit the events of the OpenC2 Consumer (e.g., command execution, * errors, and status updates). */ class ControlManager extends stream_1.EventEmitter { /** * Constructor for the ControlManager class. * @param serviceRegistrySettings - Service Registry settings, which include the consumer and * adapter configurations. * @param logger - Logger instance. * @param defaultResolver - This is the default resolver map for the OpenC2 interface, which is * merged with the resolver map from the service registry settings, if provided. The default * value, passed from the Service Registry instance include resolvers for the `features`: * - `query`: `health`, `stats`, `errors` and `config` * - `start`: `resources` * - `stop`: `resources` */ constructor(serviceRegistrySettings, logger, defaultResolver) { super(); this.serviceRegistrySettings = serviceRegistrySettings; this.logger = logger; this.defaultResolver = defaultResolver; if (cluster_1.default.isWorker) { // Stryker disable next-line all this.logger.debug(`The OpenC2 Consumer can not be instantiated in a worker process`); return; } this.instance = this.initializeOpenC2Consumer(); if (this.instance) { this.wrapConsumerEvents(this.instance); } } /** * Instantiates the OpenC2 Consumer instance based on the service registry settings. * @returns OpenC2 Consumer instance, if it was successfully instantiated. */ initializeOpenC2Consumer() { const _consumerOptions = this.getConsumerOptions(this.serviceRegistrySettings, this.defaultResolver); const _adapterOptions = this.getAdapterOptions(this.serviceRegistrySettings.adapterOptions); if (_consumerOptions) { if (_adapterOptions && _adapterOptions.type === 'socketIO') { // Stryker disable next-line all this.logger.info(`A OpenC2 Consumer [${_consumerOptions.id}] based on SocketIO is going to be instantiated.`); return openc2_1.Factory.Consumer.SocketIO(_consumerOptions, _adapterOptions.config); } else if (_adapterOptions && _adapterOptions.type === 'redis') { // Stryker disable next-line all this.logger.info(`A OpenC2 Consumer [${_consumerOptions.id}] based on Redis is going to be instantiated.`); return openc2_1.Factory.Consumer.Redis(_consumerOptions, _adapterOptions.config); } else { // Stryker disable next-line all this.logger.warn(`The OpenC2 Consumer will be instantiated with a dummy adapter, no adapter options were provided.`); return openc2_1.Factory.Consumer.Dummy(_consumerOptions); } } else if (this._error) { // Stryker disable next-line all this.logger.warn(`The OpenC2 Consumer was not instantiated due to the following errors: ${this._error.trace()}`); } else { // Stryker disable next-line all this.logger.debug(`A OpenC2 Consumer was not instantiated`); } return undefined; } /** * Returns the validation error, if exist. * @returns Multi error, if exist. */ get error() { return this._error; } /** * Checks and return a validated version of adapter options. * @param options - Consumer adapter options * @returns OpenC2 adapter options, retrieved from the service registry settings, if provided. */ getAdapterOptions(options) { let _options; if (!options) { this.logger.warn(`No consumer adapter options were provided, a dummy adapter will be created`); } else if (typeof options.type === 'string' && options.type !== 'redis' && options.type !== 'socketIO') { this.addError(new crash_1.Crash(`Unknown consumer adapter type, costumer will not be instantiated.`)); } else { _options = options; } return _options; } /** * Checks and return a validated version of consumer options. * @param options - Service registry settings * @param defaultResolver - Default resolver map for the OpenC2 interface * @returns OpenC2 consumer options, retrieved from the service registry settings, if provided and * merged with some default options. */ getConsumerOptions(options, defaultResolver) { var _a, _b, _c, _d; const _id = (_b = (_a = options.consumerOptions) === null || _a === void 0 ? void 0 : _a.id) !== null && _b !== void 0 ? _b : (_c = options.metadata) === null || _c === void 0 ? void 0 : _c.name; const _resolver = (0, lodash_1.merge)(defaultResolver, (_d = options.consumerOptions) === null || _d === void 0 ? void 0 : _d.resolver); const _actionTargetPairs = this.getActionTargetPairs(options); const _logger = this.logger; if (!_id) { this.addError(new crash_1.Crash(`No consumer id was provided in the service registry settings, this looks looks like an internal library error.`)); return undefined; } return (0, lodash_1.merge)((0, lodash_1.cloneDeep)(options.consumerOptions), { id: _id, resolver: _resolver, actionTargetPairs: _actionTargetPairs, logger: _logger, }); } /** * Constructs and merges the action-target pairs for the OpenC2 interface based on the namespace * and service registry settings. * @param options - Service registry settings * @returns Action-Target pairs for the OpenC2 interface, merged with the default pairs for the */ getActionTargetPairs(options) { var _a, _b, _c; let _defaultPairs; // If a namespace is defined, create pairs for service control by the Service Registry if ((_a = options.metadata) === null || _a === void 0 ? void 0 : _a.namespace) { _defaultPairs = { query: [ 'features', `${options.metadata.namespace}:health`, `${options.metadata.namespace}:stats`, `${options.metadata.namespace}:errors`, ], start: [`${options.metadata.namespace}:resources`], stop: [`${options.metadata.namespace}:resources`], }; } // If action-target pairs are provided in the service registry settings, use them else if ((_b = options.consumerOptions) === null || _b === void 0 ? void 0 : _b.actionTargetPairs) { _defaultPairs = {}; } // Otherwise, use the default pairs for the OpenC2 interface else { _defaultPairs = { query: ['features'] }; } return (0, lodash_1.mergeWith)(_defaultPairs, (_c = options.consumerOptions) === null || _c === void 0 ? void 0 : _c.actionTargetPairs, customizer); } /** * Adds an error to the validation error list, creating a new Multi error if necessary. If the * error is a Multi error, its causes are added to the list. * @param error - The error to add to the validation error list. */ addError(error) { if (!error) { return; } if (!this._error) { this._error = new crash_1.Multi(`Error in the OpenC2 Consumer instance configuration`); } if (error instanceof crash_1.Multi) { if (error.causes) { error.causes.forEach(cause => { var _a; (_a = this._error) === null || _a === void 0 ? void 0 : _a.push(cause); }); } else { this._error.push(error); } } else if (error instanceof crash_1.Crash) { this._error.push(error); } else { this._error.push(crash_1.Crash.from(error)); } } /** * Event handler for the `command` event emitted by the OpenC2 Consumer instance. * @param command - The OpenC2 command job handler. * @returns void */ onCommandEvent(command) { this.logger.debug(`Received command: ${JSON.stringify(command)}`); this.emit('command', command); } /** * Wraps the OpenC2 Consumer instance events with the ControlManager event handlers. * @param instance - The OpenC2 Consumer instance. * @returns void */ wrapConsumerEvents(instance) { instance.on('command', this.onCommandEvent.bind(this)); } /** * Starts the OpenC2 Consumer instance. * @returns Promise<void> */ async start() { var _a; await ((_a = this.instance) === null || _a === void 0 ? void 0 : _a.start()); } /** * Stops the OpenC2 Consumer instance. * @returns Promise<void> */ async stop() { var _a; await ((_a = this.instance) === null || _a === void 0 ? void 0 : _a.stop()); } } exports.ControlManager = ControlManager; //# sourceMappingURL=ControlManager.js.map