appdynamics
Version:
Performance Profiler and Monitor
319 lines (279 loc) • 10.6 kB
JavaScript
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();
};