@mdf.js/service-registry
Version:
MMS - API - Service Registry
244 lines • 10.5 kB
JavaScript
"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