diff --git a/CHANGELOG.md b/CHANGELOG.md index 8538e4aa..5bf9ad26 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## [2.2.0] - 2019-05-06 +### Added +- Send telemetry by command + ## [2.1.1] - 2019-05-02 ### Fixed - pxConfig setting for proxy diff --git a/README.md b/README.md index 8e7dbed1..81b8ccc3 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [PerimeterX](http://www.perimeterx.com) Shared base for NodeJS enforcers ============================================================= -> Latest stable version: [v2.1.1](https://www.npmjs.com/package/perimeterx-node-core) +> Latest stable version: [v2.2.0](https://www.npmjs.com/package/perimeterx-node-core) This is a shared base implementation for PerimeterX Express enforcer and future NodeJS enforcers. For a fully functioning implementation example, see the [Node-Express enforcer](https://github.com/PerimeterX/perimeterx-node-express/) implementation. diff --git a/lib/pxconfig.js b/lib/pxconfig.js index 59d92a39..2be5a06e 100644 --- a/lib/pxconfig.js +++ b/lib/pxconfig.js @@ -32,7 +32,8 @@ class PxConfig { ['SEND_PAGE_ACTIVITIES', 'sendPageActivities'], ['SENSITIVE_HEADERS', 'sensitiveHeaders'], ['DEBUG_MODE', 'debugMode'], ['MAX_BUFFER_LEN', 'maxBufferLength'], ['JS_REF', 'jsRef'], ['CSS_REF', 'cssRef'], ['CUSTOM_LOGO', 'customLogo'], ['SENSITIVE_ROUTES', 'sensitiveRoutes'], ['WHITELIST_ROUTES', 'whitelistRoutes'], ['DYNAMIC_CONFIGURATIONS', 'dynamicConfigurations'], ['MODULE_MODE', 'moduleMode'], ['FIRST_PARTY_ENABLED', 'firstPartyEnabled'], ['ADDITIONAL_ACTIVITY_HANDLER', 'additionalActivityHandler'], ['ENRICH_CUSTOM_PARAMETERS', 'enrichCustomParameters'], - ['TESTING_MODE', 'testingMode'], ['WHITELIST_EXT', 'whitelistExt'], ['BYPASS_MONITOR_HEADER', 'bypassMonitorHeader'], ['ADVANCED_BLOCKING_RESPONSE', 'advancedBlockingResponse']]; + ['TESTING_MODE', 'testingMode'], ['WHITELIST_EXT', 'whitelistExt'], ['BYPASS_MONITOR_HEADER', 'bypassMonitorHeader'], ['ADVANCED_BLOCKING_RESPONSE', 'advancedBlockingResponse'], + ['TELEMETRY_COMMAND_HEADER', 'telemetryCommandHeader']]; configKeyMapping.forEach(([targetKey, sourceKey]) => { this.PX_DEFAULT[targetKey] = PxConfig.configurationsOverriding(this.PX_DEFAULT, params, targetKey, sourceKey); @@ -112,6 +113,7 @@ function pxInternalConfig() { SERVER_COLLECT_URI: '/api/v1/collector/s2s', CONFIGURATIONS_URI: '/api/v1/enforcer', TELEMETRY_URI: '/api/v2/risk/telemetry', + TELEMETRY_COMMAND_HEADER: 'x-px-enforcer-telemetry', ENFORCER_TRUE_IP_HEADER: 'x-px-enforcer-true-ip', FIRST_PARTY_HEADER: 'x-px-first-party', FORWARDED_FOR_HEADER: 'x-forwarded-for', @@ -238,7 +240,8 @@ const configSchemaMapper = { px_test_mode: 'testingMode', px_whitelist_extensions: 'whitelistExt', px_bypass_monitor_header: 'bypassMonitorHeader', - px_advanced_blocking_response: 'advancedBlockingResponse' + px_advanced_blocking_response: 'advancedBlockingResponse', + px_telemetry_command: 'telemetryCommandHeader' }; module.exports = PxConfig; \ No newline at end of file diff --git a/lib/pxenforcer.js b/lib/pxenforcer.js index ad9befd4..3264955b 100644 --- a/lib/pxenforcer.js +++ b/lib/pxenforcer.js @@ -10,6 +10,7 @@ const pxApi = require('./pxapi'); const pxCookie = require('./pxcookie'); const PxLogger = require('./pxlogger'); const ConfigLoader = require('./configloader'); +const telemetryHandler = require('./telemetry_handler.js'); class PxEnforcer { constructor(params, client) { @@ -22,7 +23,6 @@ class PxEnforcer { this.pxClient = (client) ? client : new PxClient(); this.pxClient.init(this._config); - this.pxClient.sendEnforcerTelemetry('initial_config', this._config); if (this._config.DYNAMIC_CONFIGURATIONS) { this.config.configLoader = new ConfigLoader(this.pxConfig, this.pxClient); this.config.configLoader.init(); @@ -55,6 +55,14 @@ class PxEnforcer { return cb(); } + try { + if(telemetryHandler.isTelemetryCommand(req, this._config)) { + this.pxClient.sendEnforcerTelemetry('command', this._config); + } + } catch (err) { + this.logger.debug('Telemetry command failure. unexpected error. ' + err.message); + } + try { const ctx = new PxContext(this._config, req, this.logger); this.logger.debug('Request context created successfully'); diff --git a/lib/telemetry_handler.js b/lib/telemetry_handler.js new file mode 100644 index 00000000..3935f772 --- /dev/null +++ b/lib/telemetry_handler.js @@ -0,0 +1,52 @@ +const crypto = require('crypto'); +const PxLogger = require('./pxlogger'); + +module.exports = { + isTelemetryCommand +}; + +function isTelemetryCommand(req, config) { + + const logger = new PxLogger(); + const headerVal = req.headers[config.TELEMETRY_COMMAND_HEADER]; + + if(!headerVal) { + return false; + } + + logger.debug('Received command to send enforcer telemetry'); + + // base 64 decode + const decodedString = Buffer.from(headerVal, 'base64').toString(); + + // value is in the form of timestamp:hmac_val + const splittedValue = decodedString.split(':'); + + if(splittedValue.length !== 2) { + logger.debug('Malformed header value - ${config.TELEMETRY_COMMAND_HEADER} = ${headerVal}'); + return false; + } + + const [timestamp, hmac] = splittedValue; + + // timestamp + if(Number(timestamp) < new Date().getTime()) { + logger.debug('Telemetry command has expired'); + return false; + } + + // check hmac integrity + const hmacCreator = crypto.createHmac('sha256', config.COOKIE_SECRET_KEY); + hmacCreator.setEncoding('hex'); + hmacCreator.write(timestamp); + hmacCreator.end(); + const generatedHmac = hmacCreator.read(); + + if (generatedHmac !== hmac) { + logger.debug('hmac validation failed. original = ${hmac}, generated = ${generatedHmac}'); + return false; + } + + return true; +} + diff --git a/package-lock.json b/package-lock.json index 54fc3d73..3109e7c2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "perimeterx-node-core", - "version": "2.1.1", + "version": "2.2.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -339,7 +339,8 @@ "eslint-config-perimeterx": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eslint-config-perimeterx/-/eslint-config-perimeterx-0.2.0.tgz", - "integrity": "sha512-WfPR5cqmr8XR1A4LcO0tYunftG0dBmaTvhSyr9/dKNdGGPUwzaETB6P/hz15+YgpK07oookgodIvaxigbpPBpQ==" + "integrity": "sha512-WfPR5cqmr8XR1A4LcO0tYunftG0dBmaTvhSyr9/dKNdGGPUwzaETB6P/hz15+YgpK07oookgodIvaxigbpPBpQ==", + "dev": true }, "eslint-scope": { "version": "4.0.3", @@ -851,7 +852,7 @@ }, "os-tmpdir": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "resolved": "http://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", "dev": true }, @@ -866,7 +867,7 @@ }, "path-is-absolute": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "resolved": "http://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "dev": true }, diff --git a/package.json b/package.json index fc03ca83..d242c33d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "perimeterx-node-core", - "version": "2.1.1", + "version": "2.2.0", "description": "PerimeterX NodeJS shared core for various applications to monitor and block traffic according to PerimeterX risk score", "main": "index.js", "scripts": {