@contrast/core 1.32.3 → 1.33.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/agent-info.js +7 -1
- package/lib/app-info.js +1 -1
- package/lib/index.d.ts +3 -2
- package/lib/system-info/cloud-resource-identifier.js +102 -0
- package/lib/system-info/index.js +42 -39
- package/package.json +4 -3
package/lib/agent-info.js
CHANGED
|
@@ -15,10 +15,11 @@
|
|
|
15
15
|
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
|
+
const { randomUUID } = require('crypto');
|
|
18
19
|
const { name: agentName, version: agentVersion } = require('../package.json');
|
|
19
20
|
|
|
20
21
|
module.exports = function init(core) {
|
|
21
|
-
// default to version of core
|
|
22
|
+
// default to name and version of core
|
|
22
23
|
if (!core.agentName) {
|
|
23
24
|
core.agentName = agentName;
|
|
24
25
|
}
|
|
@@ -26,5 +27,10 @@ module.exports = function init(core) {
|
|
|
26
27
|
core.agentVersion = agentVersion;
|
|
27
28
|
}
|
|
28
29
|
|
|
30
|
+
// default to a new random UUID
|
|
31
|
+
if (!core.reportingInstance) {
|
|
32
|
+
core.reportingInstance = randomUUID();
|
|
33
|
+
}
|
|
34
|
+
|
|
29
35
|
return core;
|
|
30
36
|
};
|
package/lib/app-info.js
CHANGED
|
@@ -167,7 +167,7 @@ module.exports = function (core) {
|
|
|
167
167
|
|
|
168
168
|
for (dir of dirs) {
|
|
169
169
|
try {
|
|
170
|
-
packageFile = findPackageJsonSync({ cwd: dir });
|
|
170
|
+
packageFile = process.env.npm_package_json ?? findPackageJsonSync({ cwd: dir });
|
|
171
171
|
packageData = require(packageFile);
|
|
172
172
|
break;
|
|
173
173
|
} catch (err) {} // eslint-disable-line no-empty
|
package/lib/index.d.ts
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* way not consistent with the End User License Agreement.
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
import { AppInfo, Messages } from '@contrast/common';
|
|
16
|
+
import { AppInfo, Messages, SystemInfo } from '@contrast/common';
|
|
17
17
|
|
|
18
18
|
interface Frame {
|
|
19
19
|
eval: string | undefined;
|
|
@@ -32,6 +32,7 @@ interface CreateSnapshotOpts {
|
|
|
32
32
|
export interface Core {
|
|
33
33
|
agentName: string;
|
|
34
34
|
agentVersion: string;
|
|
35
|
+
reportingInstance: string;
|
|
35
36
|
|
|
36
37
|
appInfo: AppInfo;
|
|
37
38
|
|
|
@@ -44,5 +45,5 @@ export interface Core {
|
|
|
44
45
|
|
|
45
46
|
sensitiveDataMasking: any;
|
|
46
47
|
|
|
47
|
-
getSystemInfo():
|
|
48
|
+
getSystemInfo(): Promise<SystemInfo>;
|
|
48
49
|
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright: 2024 Contrast Security, Inc
|
|
3
|
+
* Contact: support@contrastsecurity.com
|
|
4
|
+
* License: Commercial
|
|
5
|
+
|
|
6
|
+
* NOTICE: This Software and the patented inventions embodied within may only be
|
|
7
|
+
* used as part of Contrast Security’s commercial offerings. Even though it is
|
|
8
|
+
* made available through public repositories, use of this Software is subject to
|
|
9
|
+
* the applicable End User Licensing Agreement found at
|
|
10
|
+
* https://www.contrastsecurity.com/enduser-terms-0317a or as otherwise agreed
|
|
11
|
+
* between Contrast Security and the End User. The Software may not be reverse
|
|
12
|
+
* engineered, modified, repackaged, sold, redistributed or otherwise used in a
|
|
13
|
+
* way not consistent with the End User License Agreement.
|
|
14
|
+
*/
|
|
15
|
+
// @ts-check
|
|
16
|
+
'use strict';
|
|
17
|
+
|
|
18
|
+
const { default: axios } = require('axios');
|
|
19
|
+
const METADATA_ENDPOINT_ADDRESS = 'http://169.254.169.254';
|
|
20
|
+
|
|
21
|
+
/** @param {number} ms */
|
|
22
|
+
const abort = (ms) => {
|
|
23
|
+
const abortController = new AbortController();
|
|
24
|
+
setTimeout(() => abortController.abort(), ms);
|
|
25
|
+
return abortController.signal;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const FETCHERS = {
|
|
29
|
+
/**
|
|
30
|
+
* @see https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instancedata-data-retrieval.html
|
|
31
|
+
* @returns {Promise<string | null>}
|
|
32
|
+
*/
|
|
33
|
+
async AWS() {
|
|
34
|
+
try {
|
|
35
|
+
const { data: token } = await axios({
|
|
36
|
+
method: 'PUT',
|
|
37
|
+
url: new URL('/latest/api/token', METADATA_ENDPOINT_ADDRESS).href,
|
|
38
|
+
headers: {
|
|
39
|
+
'X-aws-ec2-metadata-token-ttl-seconds': '300'
|
|
40
|
+
},
|
|
41
|
+
signal: abort(5000),
|
|
42
|
+
});
|
|
43
|
+
const { data } = await axios({
|
|
44
|
+
method: 'GET',
|
|
45
|
+
url: new URL('/latest/dynamic/instance-identity/document', METADATA_ENDPOINT_ADDRESS).href,
|
|
46
|
+
headers: {
|
|
47
|
+
'X-aws-ec2-metadata-token': token
|
|
48
|
+
},
|
|
49
|
+
signal: abort(5000),
|
|
50
|
+
});
|
|
51
|
+
if (data) {
|
|
52
|
+
const { region, accountId, instanceId } = data;
|
|
53
|
+
return `arn:aws:ec2:${region}:${accountId}:instance/${instanceId}`;
|
|
54
|
+
}
|
|
55
|
+
} catch {
|
|
56
|
+
// ignore, return null
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return null;
|
|
60
|
+
},
|
|
61
|
+
/**
|
|
62
|
+
* @see https://learn.microsoft.com/en-us/azure/virtual-machines/instance-metadata-service?tabs=linux
|
|
63
|
+
* @returns {Promise<string | null>}
|
|
64
|
+
*/
|
|
65
|
+
async Azure() {
|
|
66
|
+
try {
|
|
67
|
+
const { data } = await axios({
|
|
68
|
+
method: 'GET',
|
|
69
|
+
url: new URL('/metadata/instance/compute/resourceId?api-version=2021-02-01&format=text', METADATA_ENDPOINT_ADDRESS).href,
|
|
70
|
+
headers: {
|
|
71
|
+
Metadata: 'true'
|
|
72
|
+
},
|
|
73
|
+
proxy: false, // You *must* bypass proxies when querying IMDS
|
|
74
|
+
signal: abort(5000),
|
|
75
|
+
});
|
|
76
|
+
if (data) return data;
|
|
77
|
+
} catch {
|
|
78
|
+
// ignore, return null
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
module.exports = {
|
|
86
|
+
/**
|
|
87
|
+
* @param {import('@contrast/config').Config['inventory']['gather_metadata_via']} provider
|
|
88
|
+
* @returns {Promise<string | null>}
|
|
89
|
+
*/
|
|
90
|
+
async getResourceID(provider) {
|
|
91
|
+
if (provider && FETCHERS[provider]) return FETCHERS[provider]();
|
|
92
|
+
|
|
93
|
+
const results = await Promise.allSettled(Object.values(FETCHERS).map(fn => fn()));
|
|
94
|
+
for (const result of results) {
|
|
95
|
+
if (result.status === 'fulfilled' && result.value !== null) {
|
|
96
|
+
return result.value;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
};
|
package/lib/system-info/index.js
CHANGED
|
@@ -12,52 +12,45 @@
|
|
|
12
12
|
* engineered, modified, repackaged, sold, redistributed or otherwise used in a
|
|
13
13
|
* way not consistent with the End User License Agreement.
|
|
14
14
|
*/
|
|
15
|
-
|
|
15
|
+
// @ts-check
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
|
-
const
|
|
19
|
-
const fs = require('fs');
|
|
18
|
+
const fs = require('fs/promises');
|
|
20
19
|
const os = require('os');
|
|
20
|
+
const { getResourceID } = require('./cloud-resource-identifier');
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
version = require(path.join(packagePath, 'package.json')).dependencies['pm2'];
|
|
31
|
-
} catch (err) {
|
|
32
|
-
//
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
if (version) break;
|
|
22
|
+
const MOUNTINFO_REGEX = /\/docker\/containers\/(.*?)\//;
|
|
23
|
+
const CGROUP_REGEX = /:\/docker\/([^/]+)$/;
|
|
24
|
+
|
|
25
|
+
function isUsingPM2(pkg) {
|
|
26
|
+
const result = { used: !!process.env.pmx, version: null };
|
|
27
|
+
|
|
28
|
+
if (pkg?.dependences?.['pm2']) {
|
|
29
|
+
result.version = pkg.dependencies['pm2'];
|
|
36
30
|
}
|
|
37
31
|
|
|
38
|
-
return
|
|
32
|
+
return result;
|
|
39
33
|
}
|
|
40
34
|
|
|
41
|
-
function isDocker() {
|
|
42
|
-
const MOUNTINFO_REGEX = /\/docker\/containers\/(.*?)\//;
|
|
43
|
-
const CGROUP_REGEX = /:\/docker\/([^/]+)$/;
|
|
44
|
-
|
|
35
|
+
async function isDocker() {
|
|
45
36
|
try {
|
|
46
|
-
const
|
|
47
|
-
|
|
37
|
+
const result = await fs.readFile('/proc/self/mountinfo', 'utf8');
|
|
38
|
+
const matches = result.match(MOUNTINFO_REGEX);
|
|
39
|
+
if (matches) return { isDocker: true, containerID: matches[1] };
|
|
48
40
|
} catch (err) {
|
|
49
41
|
// else check /proc/self/cgroup
|
|
50
42
|
}
|
|
51
43
|
|
|
52
44
|
try {
|
|
53
|
-
const
|
|
54
|
-
|
|
45
|
+
const result = await fs.readFile('/proc/self/cgroup', 'utf8');
|
|
46
|
+
const matches = result.match(CGROUP_REGEX);
|
|
47
|
+
if (matches) return { isDocker: true, containerID: matches[1] };
|
|
55
48
|
} catch (err) {
|
|
56
49
|
// else check /.dockerenv
|
|
57
50
|
}
|
|
58
51
|
|
|
59
52
|
try {
|
|
60
|
-
const result = fs.
|
|
53
|
+
const result = await fs.stat('/.dockerenv');
|
|
61
54
|
if (result) return { isDocker: true, containerID: null };
|
|
62
55
|
} catch (err) {
|
|
63
56
|
// if there's not such file we can conclude it's not docker env
|
|
@@ -70,12 +63,18 @@ module.exports = function(core) {
|
|
|
70
63
|
const {
|
|
71
64
|
agentName,
|
|
72
65
|
agentVersion,
|
|
73
|
-
config
|
|
66
|
+
config,
|
|
67
|
+
appInfo,
|
|
74
68
|
} = core;
|
|
75
69
|
|
|
76
70
|
// have values default to null so all required keys get serialized
|
|
77
|
-
core.getSystemInfo = function() {
|
|
78
|
-
|
|
71
|
+
core.getSystemInfo = async function getSystemInfo() {
|
|
72
|
+
// memoize for subsequent lookups
|
|
73
|
+
if (core._systemInfo) return core._systemInfo;
|
|
74
|
+
|
|
75
|
+
const cpus = os.cpus();
|
|
76
|
+
const totalmem = os.totalmem();
|
|
77
|
+
const freemem = os.freemem();
|
|
79
78
|
|
|
80
79
|
const info = {
|
|
81
80
|
ReportDate: new Date().toISOString(),
|
|
@@ -103,22 +102,26 @@ module.exports = function(core) {
|
|
|
103
102
|
Version: os.release(),
|
|
104
103
|
KernelVersion: os.version(),
|
|
105
104
|
CPU: {
|
|
106
|
-
Type:
|
|
107
|
-
Count:
|
|
105
|
+
Type: cpus[0].model,
|
|
106
|
+
Count: cpus.length,
|
|
108
107
|
}
|
|
109
108
|
},
|
|
110
109
|
Host: {
|
|
111
|
-
Docker: isDocker(),
|
|
112
|
-
PM2: isUsingPM2(),
|
|
110
|
+
Docker: await isDocker(),
|
|
111
|
+
PM2: isUsingPM2(appInfo.pkg),
|
|
113
112
|
Memory: {
|
|
114
|
-
Total: (
|
|
115
|
-
Free: (
|
|
116
|
-
Used: ((
|
|
113
|
+
Total: (totalmem / 1e6).toFixed(0).concat(' MB'),
|
|
114
|
+
Free: (freemem / 1e6).toFixed(0).concat(' MB'),
|
|
115
|
+
Used: ((totalmem - freemem) / 1e6).toFixed(0).concat(' MB'),
|
|
117
116
|
}
|
|
118
117
|
},
|
|
119
|
-
Application:
|
|
118
|
+
Application: appInfo.pkg,
|
|
120
119
|
};
|
|
121
120
|
|
|
122
|
-
|
|
121
|
+
if (config.server.discover_cloud_resource) {
|
|
122
|
+
info.ResourceID = await getResourceID(config.inventory.gather_metadata_via);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return core._systemInfo = info;
|
|
123
126
|
};
|
|
124
127
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contrast/core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.33.0",
|
|
4
4
|
"description": "Preconfigured Contrast agent core services and models",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"author": "Contrast Security <nodejs@contrastsecurity.com> (https://www.contrastsecurity.com)",
|
|
@@ -16,8 +16,9 @@
|
|
|
16
16
|
"test": "../scripts/test.sh"
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@contrast/common": "1.
|
|
19
|
+
"@contrast/common": "1.22.0",
|
|
20
20
|
"@contrast/find-package-json": "^1.0.0",
|
|
21
|
-
"@contrast/fn-inspect": "^4.0.0"
|
|
21
|
+
"@contrast/fn-inspect": "^4.0.0",
|
|
22
|
+
"axios": "^1.6.8"
|
|
22
23
|
}
|
|
23
24
|
}
|