UNPKG

appdynamics

Version:

Performance Profiler and Monitor

319 lines (279 loc) 10.6 kB
const http = require('http'); const https = require('https'); const url = require('url'); const HttpsProxyAgent = require('https-proxy-agent'); const LibraryMetadata = require('./library_metadata').LibraryMetadata; const MessageSender = require('../libagent/message-sender').MessageSender; const AUTH_PATH = "/auth/v1/oauth/token"; const EVENT_PATH = "/argento-agent/v1/report"; const REGISTER_PATH = "/argento-agent/v1/management"; const MIN_TIMER = 60 * 1000; const DAY_TIMER = 24 * 60 * 60 * 1000; const RETRY_THRESHOLD = 2; const TIMEOUT = 1000; exports.SecureApp = SecureApp; function SecureApp() { } SecureApp.prototype.init = function(agent) { var self = this; if(agent.opts.certificateFile) { try { self.certificateFile = require('fs').readFileSync(agent.opts.certificateFile); } catch (e) { self.agent.logger.WARN('Secure App could not read certificate file'); } } self.agent = agent; self.libraryMetadata = new LibraryMetadata(agent); if(!process.env.SKIP_AUTH_FOR_TESTS) { self.authTimerId = new MessageSender(self.agent, 10 * 1000, MIN_TIMER * 9, function () { self.authToken(); }); } else { self.agent.logger.debug('Secure App skipping auth as SKIP_AUTH_FOR_TESTS is ' + process.env.SKIP_AUTH_FOR_TESTS); self.regTimerId = new MessageSender(self.agent, 10 * 1000, MIN_TIMER, function () { self.register(); }); } self.httpModule = (self.agent.opts.controllerSslEnabled) ? https : http; self.libraryMetadata.init(); self.agent.logger.debug('Secure App module initialized'); }; SecureApp.prototype._sendRequestWithRetry = function(options, data, cb) { var self = this; options.currentRetryAttempt = 0; self._sendRequest(options, data, cb); }; SecureApp.prototype._retryRequest = function(options, data, cb) { var self = this; if (options.currentRetryAttempt === RETRY_THRESHOLD) { return cb(null, new Error('Retry Threshold Reached')); } options.currentRetryAttempt++; self.agent.logger.trace('SecureApp._retryRequest: Retry attempt ' + options.currentRetryAttempt + ' ' + options.path); setTimeout(() => { self._sendRequest(options, data, cb); }, TIMEOUT); }; SecureApp.prototype._sendRequest = function(options, data, cb) { var self = this; self.agent.logger.trace('SecureApp._sendRequest: ' + options.path + " Data: " + ((options.headers['Content-Type'] != 'application/json') ? data.length.toString() : JSON.stringify(data).length.toString())); var request = self.httpModule.request(options, function(response) { const chunks = []; response.on('data', data_chunk => chunks.push(data_chunk)); response.on('end', () => { var statusCode = response.statusCode | 0; // don't retry on http 413 (payload too large) if(statusCode == 413) { self.agent.logger.warn('Secure App Http Request failed ' + ' statusCode ' + statusCode); return cb(null, new Error('Http Payload too large')); } if (statusCode != 200 || response.is_error) { self.agent.logger.info('Secure App Http Request failed ' + ' statusCode ' + statusCode + ' is_error ' + response.is_error); return self._retryRequest(options, data, cb); } let body = Buffer.concat(chunks); cb(body, null); }); }); request.on('error', function(error) { self.agent.logger.info('Secure app http request error : ' + error); return self._retryRequest(options, data, cb); }); request.end(data); }; SecureApp.prototype._requestHeaders = function(requestOptions) { var self = this; requestOptions.headers['Authorization'] = `Bearer ${self.accessToken}`; requestOptions.headers['User-Agent'] = "NodeJs"; requestOptions.headers['appdynamics-agent-applicationName'] = self.agent.opts.applicationName; requestOptions.headers['appdynamics-agent-tierName'] = self.agent.opts.tierName; requestOptions.headers['appdynamics-agent-nodeName'] = self.agent.opts.nodeName; requestOptions.headers['appdynamics-agent-accountName'] = 'singularity-agent@' + self.agent.opts.accountName; if (self.uuid) { requestOptions.headers['appdynamics-agent-nodeUUID'] = self.uuid; } }; SecureApp.prototype._requestOptions = function(path, length, type) { var self = this; var requestOptions = { 'method': 'POST', }; requestOptions.headers = { 'Content-Length': length, 'Content-Type': type }; if (self.certificateFile) { requestOptions['ca'] = self.certificateFile; } if(process.env.CONTROLLER_HOST_TEST) { self.agent.logger.debug('Secure App using controller hostname CONTROLLER_HOST_TEST: ' + process.env.CONTROLLER_HOST_TEST); requestOptions['hostname'] = process.env.CONTROLLER_HOST_TEST; } else { requestOptions['hostname'] = self.agent.opts.controllerHostName; } if(process.env.CONTROLLER_PORT_TEST) { self.agent.logger.debug('Secure App using controller port CONTROLLER_PORT_TEST: ' + process.env.CONTROLLER_PORT_TEST); requestOptions['port'] = process.env.CONTROLLER_PORT_TEST; } else { requestOptions['port'] = self.agent.opts.controllerPort; } requestOptions['path'] = path; var proxy = { hostName: self.agent.opts.proxyHost, port: self.agent.opts.proxyPort, userName: self.agent.opts.proxyUser, password: "" }; if (self.agent.opts.proxyPasswordFile) { var fs = require('fs'); proxy.password = (fs.readFileSync(self.agent.opts.proxyPasswordFile, 'utf-8')).trim(); } if (proxy.hostName) { self.agent.logger.debug('Secure App using proxy'); var ro = requestOptions; var proxyAuth = proxy.userName && proxy.password && Buffer.from(`${proxy.userName}:${proxy.password}`).toString('base64'); if (self.agent.opts.controllerSslEnabled) { var proxyUrl = `http://${proxy.hostName}:${proxy.port}`; var proxyOpts = url.parse(proxyUrl); if (proxyAuth) { proxyOpts.headers = { 'Proxy-Authentication': `Basic ${proxyAuth}` }; } var agent = new HttpsProxyAgent(proxyOpts); ro['agent'] = agent; } else { ro['path'] = `http://${ro.hostname}:${ro.port}${ro.path}`; ro['headers']['Host'] = `${ro.hostname}:${ro.port}`; ro['hostname'] = proxy.hostName; ro['port'] = proxy.port; if (proxyAuth) { ro['headers']['Proxy-Authorization'] = `Basic ${proxyAuth}`; } } } return requestOptions; }; SecureApp.prototype._initializeTimers = function() { var self = this; if (self.timersInitialized) { return; } self.timersInitialized = true; self.reportEventTimerId = new MessageSender(self.agent, 10 * 1000, DAY_TIMER, function () { self.reportEvents(); }); }; SecureApp.prototype.authenticate = function(grantType) { var self = this; var postData = "password=" + self.agent.opts.accountAccessKey + "&username=" + 'singularity-agent@' + self.agent.opts.accountName + "&grant_type=" + grantType; if(grantType == 'refresh_token') { postData += '&refresh_token=' + self.refreshToken; } var requestOptions = self._requestOptions(AUTH_PATH, postData.length.toString(), "application/x-www-form-urlencoded; charset=utf-8"); self._sendRequestWithRetry(requestOptions, postData, function(body, error) { if (error != null) { self.agent.logger.info('Secure App authentication failed'); self.accessToken = null; self.refreshToken = null; return; } try { body = JSON.parse(body); if (!self.accessToken) { self.regTimerId = new MessageSender(self.agent, 10 * 1000, MIN_TIMER, function () { self.register(); }); } self.accessToken = body.access_token; self.refreshToken = body.refresh_token; self.agent.logger.debug('Secure App module authenticated'); } catch(err) { self.accessToken = null; self.refreshToken = null; self.agent.logger.warn('Failed to parse Secure app authentication response ' + err); } }); }; SecureApp.prototype.authToken = function() { var self = this; if(!self.accessToken) { self.authenticate("password"); } else { self.authenticate("refresh_token"); } }; SecureApp.prototype.register = function() { var self = this; var postData = JSON.stringify({ "message_type": 0, "app": self.agent.opts.applicationName, "tier": self.agent.opts.tierName, "node": self.agent.opts.nodeName, "access_key": self.agent.opts.accountAccessKey, "account_name": self.agent.opts.accountName, "file_name": "", "epoch_msec": 0, "force": false, "version": 1, "is_started": true, "is_enabled": true, "agent_type": "NodeJs", "agent_build_version": "" }); var requestOptions = self._requestOptions(REGISTER_PATH, postData.length.toString(), "application/json"); self._requestHeaders(requestOptions); self._sendRequestWithRetry(requestOptions, postData, function(body, error) { if (error != null) { self.agent.logger.info('Secure App module registeration failed'); return; } try { body = JSON.parse(body); self.uuid = body.node_uuid; self._initializeTimers(); self.agent.logger.debug('Secure App module registered'); } catch(err) { self.agent.logger.warn('Failed to parse Secure app registration response ' + err); } }); }; SecureApp.prototype.sendVulnerabilityEvent = function() { var self = this; if(!self.vulnerabilityEvent) { return; } self.vulnerabilityEvent.forEach((data, index) => { var isDone = index == self.vulnerabilityEvent.length - 1 ? true : false; var postData = JSON.stringify({ "id": 0, "is_done": isDone, "fragment_index": index, "max_fragments": self.vulnerabilityEvent.length, "nodejs_report_component_vulnerability_list": data }); var requestOptions = self._requestOptions(EVENT_PATH, postData.length.toString(), "application/json"); self._requestHeaders(requestOptions); self._sendRequestWithRetry(requestOptions, postData, function(body, error) { if (error != null) { self.agent.logger.info('Secure App module vulnerability event failed'); return; } self.agent.logger.info('Secure App module vulnerability event sent fragment'); }); }); }; SecureApp.prototype.reportEvents = function() { var self = this; if(!self.vulnerabilityEvent) { self.vulnerabilityEvent = self.libraryMetadata.getMetadata(); } self.sendVulnerabilityEvent(); };