@contrast/core 1.41.0 → 1.42.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/index.d.ts
CHANGED
package/lib/system-info/index.js
CHANGED
|
@@ -19,6 +19,7 @@ const fs = require('fs/promises');
|
|
|
19
19
|
const os = require('os');
|
|
20
20
|
const { getCloudProviderMetadata } = require('./cloud-provider-metadata');
|
|
21
21
|
const { primordials: { StringPrototypeConcat, StringPrototypeMatch } } = require('@contrast/common');
|
|
22
|
+
const getLinuxOsInfo = require('./linux-os-info');
|
|
22
23
|
|
|
23
24
|
const MOUNTINFO_REGEX = /\/docker\/containers\/(.*?)\//;
|
|
24
25
|
const CGROUP_REGEX = /:\/docker\/([^/]+)$/;
|
|
@@ -33,10 +34,9 @@ function isUsingPM2(pkg) {
|
|
|
33
34
|
return result;
|
|
34
35
|
}
|
|
35
36
|
|
|
36
|
-
async function
|
|
37
|
+
async function getDockerInfo() {
|
|
37
38
|
try {
|
|
38
39
|
const result = await fs.readFile('/proc/self/mountinfo', 'utf8');
|
|
39
|
-
// @ts-expect-error readFile with encoding returns a string, not a Buffer
|
|
40
40
|
const matches = StringPrototypeMatch.call(result, MOUNTINFO_REGEX);
|
|
41
41
|
if (matches) return { isDocker: true, containerID: matches[1] };
|
|
42
42
|
} catch (err) {
|
|
@@ -45,7 +45,6 @@ async function isDocker() {
|
|
|
45
45
|
|
|
46
46
|
try {
|
|
47
47
|
const result = await fs.readFile('/proc/self/cgroup', 'utf8');
|
|
48
|
-
// @ts-expect-error readFile with encoding returns a string, not a Buffer
|
|
49
48
|
const matches = StringPrototypeMatch.call(result, CGROUP_REGEX);
|
|
50
49
|
if (matches) return { isDocker: true, containerID: matches[1] };
|
|
51
50
|
} catch (err) {
|
|
@@ -62,6 +61,10 @@ async function isDocker() {
|
|
|
62
61
|
return { isDocker: false, containerID: null };
|
|
63
62
|
}
|
|
64
63
|
|
|
64
|
+
function getKubernetesInfo() {
|
|
65
|
+
return { isKubernetes: !!process.env.KUBERNETES_SERVICE_HOST };
|
|
66
|
+
}
|
|
67
|
+
|
|
65
68
|
module.exports = function(core) {
|
|
66
69
|
const {
|
|
67
70
|
agentName,
|
|
@@ -79,6 +82,16 @@ module.exports = function(core) {
|
|
|
79
82
|
const totalmem = os.totalmem();
|
|
80
83
|
const freemem = os.freemem();
|
|
81
84
|
|
|
85
|
+
let linuxOsInfo = undefined;
|
|
86
|
+
const osInfo = await getLinuxOsInfo();
|
|
87
|
+
if (osInfo && osInfo.file) {
|
|
88
|
+
linuxOsInfo = {
|
|
89
|
+
Id: osInfo.id,
|
|
90
|
+
VersionId: osInfo.version_id,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
|
|
82
95
|
/** @type {import('@contrast/common').SystemInfo} */
|
|
83
96
|
const info = {
|
|
84
97
|
ReportDate: new Date().toISOString(),
|
|
@@ -109,16 +122,19 @@ module.exports = function(core) {
|
|
|
109
122
|
CPU: {
|
|
110
123
|
Type: cpus[0].model,
|
|
111
124
|
Count: cpus.length,
|
|
112
|
-
}
|
|
125
|
+
},
|
|
126
|
+
// Id, VersionId if linux, else null
|
|
127
|
+
...linuxOsInfo,
|
|
113
128
|
},
|
|
114
129
|
Host: {
|
|
115
|
-
Docker: await
|
|
130
|
+
Docker: await getDockerInfo(),
|
|
131
|
+
Kubernetes: getKubernetesInfo(),
|
|
116
132
|
PM2: isUsingPM2(appInfo.pkg),
|
|
117
133
|
Memory: {
|
|
118
134
|
Total: StringPrototypeConcat.call((totalmem / 1e6).toFixed(0), ' MB'),
|
|
119
135
|
Free: StringPrototypeConcat.call((freemem / 1e6).toFixed(0), ' MB'),
|
|
120
136
|
Used: StringPrototypeConcat.call(((totalmem - freemem) / 1e6).toFixed(0), ' MB'),
|
|
121
|
-
}
|
|
137
|
+
},
|
|
122
138
|
},
|
|
123
139
|
Application: appInfo.pkg,
|
|
124
140
|
Cloud: {
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/* eslint-disable header/header */
|
|
2
|
+
/**
|
|
3
|
+
* MIT License
|
|
4
|
+
*
|
|
5
|
+
* Copyright (c) 2018 Samuel Carreira
|
|
6
|
+
*
|
|
7
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
8
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
9
|
+
* in the Software without restriction, including without limitation the rights
|
|
10
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
11
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
12
|
+
* furnished to do so, subject to the following conditions:
|
|
13
|
+
*
|
|
14
|
+
* The above copyright notice and this permission notice shall be included in all
|
|
15
|
+
* copies or substantial portions of the Software.
|
|
16
|
+
*
|
|
17
|
+
* This code is modified from https://github.com/bmacnaughton/linux-os-info, a
|
|
18
|
+
* fork of https://github.com/samuelcarreira/linux-release-info.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/*
|
|
22
|
+
* Copyright: 2024 Contrast Security, Inc
|
|
23
|
+
* Contact: support@contrastsecurity.com
|
|
24
|
+
* License: Commercial
|
|
25
|
+
|
|
26
|
+
* NOTICE: This Software and the patented inventions embodied within may only be
|
|
27
|
+
* used as part of Contrast Security’s commercial offerings. Even though it is
|
|
28
|
+
* made available through public repositories, use of this Software is subject to
|
|
29
|
+
* the applicable End User Licensing Agreement found at
|
|
30
|
+
* https://www.contrastsecurity.com/enduser-terms-0317a or as otherwise agreed
|
|
31
|
+
* between Contrast Security and the End User. The Software may not be reverse
|
|
32
|
+
* engineered, modified, repackaged, sold, redistributed or otherwise used in a
|
|
33
|
+
* way not consistent with the End User License Agreement.
|
|
34
|
+
*/
|
|
35
|
+
'use strict';
|
|
36
|
+
|
|
37
|
+
const fsp = require('node:fs/promises');
|
|
38
|
+
const os = require('node:os');
|
|
39
|
+
|
|
40
|
+
//
|
|
41
|
+
// the key pieces of information from os-release are:
|
|
42
|
+
// - id: 'ubuntu' or 'alpine' or 'arch' etc.
|
|
43
|
+
// - version_id: ubuntu '22.04', alpine '3.20.3', arch '20241110.0.278197'
|
|
44
|
+
// - version: ubuntu '24.04.1 LTS (Noble Numbat)', alpine undefined, arch undefined
|
|
45
|
+
// NOTE: only ubuntu omits the patch field of the version in version ID. ubuntu
|
|
46
|
+
// includes a version field that has that information, but that's not present in
|
|
47
|
+
// most other distributions.
|
|
48
|
+
// the alpine-release file only contains the version string
|
|
49
|
+
// so fill in the basics based on that.
|
|
50
|
+
function addEtcAlpineReleaseToOutputData(data, outputData) {
|
|
51
|
+
if (data[data.length - 1] === '\n') {
|
|
52
|
+
data = data.slice(0, -1);
|
|
53
|
+
}
|
|
54
|
+
outputData.name = 'Alpine';
|
|
55
|
+
outputData.id = 'alpine';
|
|
56
|
+
outputData.version = data;
|
|
57
|
+
outputData.version_id = data;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const defaultList = [
|
|
61
|
+
{ path: '/etc/os-release', parser: addOsReleaseToOutputData },
|
|
62
|
+
{ path: '/usr/lib/os-release', parser: addOsReleaseToOutputData },
|
|
63
|
+
{ path: '/etc/alpine-release', parser: addEtcAlpineReleaseToOutputData }
|
|
64
|
+
];
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Get OS release info with information from '/etc/os-release', '/usr/lib/os-release',
|
|
68
|
+
* or '/etc/alpine-release'. The information in that file is distribution-dependent.
|
|
69
|
+
*
|
|
70
|
+
* @returns Promise<Object> - where object is null if not Linux, or an object with
|
|
71
|
+
* the a file property and the key-value pairs from the file. Any quotes around the
|
|
72
|
+
* values are removed.
|
|
73
|
+
*
|
|
74
|
+
* the file property in the info object will be filled in with one of:
|
|
75
|
+
* - the file path (above) used
|
|
76
|
+
* - undefined if no file was found/could be read
|
|
77
|
+
*/
|
|
78
|
+
async function linuxOsInfo(opts = {}) {
|
|
79
|
+
// allow searching for other files and parsers.
|
|
80
|
+
const list = Array.isArray(opts.list) ? opts.list : defaultList;
|
|
81
|
+
|
|
82
|
+
if (os.type() !== 'Linux') {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
let i = 0;
|
|
87
|
+
while (i < list.length) {
|
|
88
|
+
try {
|
|
89
|
+
const file = list[i].path;
|
|
90
|
+
const data = await fsp.readFile(file, 'utf8');
|
|
91
|
+
const outputData = { file };
|
|
92
|
+
list[i].parser(data, outputData);
|
|
93
|
+
|
|
94
|
+
return outputData;
|
|
95
|
+
|
|
96
|
+
} catch (e) {
|
|
97
|
+
i += 1;
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// no file could be found and read
|
|
103
|
+
return { file: undefined };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
function addOsReleaseToOutputData(data, outputData) {
|
|
108
|
+
const lines = data.split('\n');
|
|
109
|
+
|
|
110
|
+
for (let i = 0; i < lines.length; i++) {
|
|
111
|
+
const line = lines[i];
|
|
112
|
+
const index = line.indexOf('=');
|
|
113
|
+
// only look at lines with a key of length 1 or greater
|
|
114
|
+
if (index < 1) {
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// lowercase key. all the keys i've seen have been UPPERCASE but it's safer
|
|
119
|
+
// to normalize them.
|
|
120
|
+
const key = line.slice(0, index).toLowerCase();
|
|
121
|
+
// remove quotes around value. this handles a quoted value with embedded
|
|
122
|
+
// quotes even though i've never seen that in the wild.
|
|
123
|
+
let value = line.slice(index + 1).trim();
|
|
124
|
+
if (value[0] === '"' && value[value.length - 1] === '"') {
|
|
125
|
+
value = value.slice(1, -1);
|
|
126
|
+
value = value.replace(/\\"/g, '"');
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
outputData[key] = value;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
module.exports = linuxOsInfo;
|
|
134
|
+
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fsp = require('node:fs/promises');
|
|
4
|
+
const os = require('node:os');
|
|
5
|
+
const { expect } = require('chai');
|
|
6
|
+
const sinon = require('sinon');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
# cat /etc/os-release
|
|
10
|
+
NAME="Alpine Linux"
|
|
11
|
+
ID=alpine
|
|
12
|
+
VERSION_ID=3.20.3
|
|
13
|
+
PRETTY_NAME="Alpine Linux v3.20"
|
|
14
|
+
HOME_URL="https://alpinelinux.org/"
|
|
15
|
+
BUG_REPORT_URL="https://gitlab.alpinelinux.org/alpine/aports/-/issues"
|
|
16
|
+
# cat /etc/alpine-release
|
|
17
|
+
3.20.3
|
|
18
|
+
|
|
19
|
+
# cat /etc/os-release
|
|
20
|
+
PRETTY_NAME="Ubuntu 22.04.3 LTS"
|
|
21
|
+
NAME="Ubuntu"
|
|
22
|
+
VERSION_ID="22.04"
|
|
23
|
+
VERSION="22.04.3 LTS (Jammy Jellyfish)"
|
|
24
|
+
VERSION_CODENAME=jammy
|
|
25
|
+
ID=ubuntu
|
|
26
|
+
ID_LIKE=debian
|
|
27
|
+
HOME_URL="https://www.ubuntu.com/"
|
|
28
|
+
SUPPORT_URL="https://help.ubuntu.com/"
|
|
29
|
+
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
|
|
30
|
+
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
|
|
31
|
+
UBUNTU_CODENAME=jammy
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
const linuxOsInfo = require('./linux-os-info');
|
|
35
|
+
|
|
36
|
+
describe('linux-os-info', function() {
|
|
37
|
+
let fsStub;
|
|
38
|
+
let osStub;
|
|
39
|
+
|
|
40
|
+
beforeEach(function() {
|
|
41
|
+
fsStub = sinon.stub(fsp, 'readFile');
|
|
42
|
+
// fake up so test passes on windows.
|
|
43
|
+
osStub = sinon.stub(os, 'type').returns('Linux');
|
|
44
|
+
});
|
|
45
|
+
afterEach(function() {
|
|
46
|
+
fsStub.restore();
|
|
47
|
+
osStub.restore();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('should return info for Ubuntu', async function() {
|
|
51
|
+
fsStub.withArgs('/etc/os-release').resolves('PRETTY_NAME="Ubuntu 22.04.3 LTS"\nNAME="Ubuntu"\nVERSION_ID="22.04"\nVERSION="22.04.3 LTS (Jammy Jellyfish)"\nVERSION_CODENAME=jammy\nID=ubuntu\nID_LIKE=debian\nHOME_URL="https://www.ubuntu.com/"\nSUPPORT_URL="https://help.ubuntu.com/"\nBUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"\nPRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"\nUBUNTU_CODENAME=jammy\n');
|
|
52
|
+
fsStub.withArgs('/etc/alpine-release').rejects(new Error('ENOENT'));
|
|
53
|
+
|
|
54
|
+
const result = await linuxOsInfo();
|
|
55
|
+
expect(result).to.deep.equal({
|
|
56
|
+
file: '/etc/os-release',
|
|
57
|
+
pretty_name: 'Ubuntu 22.04.3 LTS',
|
|
58
|
+
name: 'Ubuntu',
|
|
59
|
+
id: 'ubuntu',
|
|
60
|
+
version_id: '22.04',
|
|
61
|
+
version: '22.04.3 LTS (Jammy Jellyfish)',
|
|
62
|
+
version_codename: 'jammy',
|
|
63
|
+
id_like: 'debian',
|
|
64
|
+
home_url: 'https://www.ubuntu.com/',
|
|
65
|
+
support_url: 'https://help.ubuntu.com/',
|
|
66
|
+
bug_report_url: 'https://bugs.launchpad.net/ubuntu/',
|
|
67
|
+
privacy_policy_url: 'https://www.ubuntu.com/legal/terms-and-policies/privacy-policy',
|
|
68
|
+
ubuntu_codename: 'jammy',
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('should return info for Alpine without an os-release file', async function() {
|
|
73
|
+
fsStub.withArgs('/etc/os-release').rejects(new Error('ENOENT'));
|
|
74
|
+
fsStub.withArgs('/usr/lib/os-release').rejects(new Error('ENOENT'));
|
|
75
|
+
fsStub.withArgs('/etc/alpine-release').resolves('3.20.3\n');
|
|
76
|
+
|
|
77
|
+
const result = await linuxOsInfo();
|
|
78
|
+
expect(result).to.deep.equal({
|
|
79
|
+
file: '/etc/alpine-release',
|
|
80
|
+
name: 'Alpine',
|
|
81
|
+
id: 'alpine',
|
|
82
|
+
version: '3.20.3',
|
|
83
|
+
version_id: '3.20.3',
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('should return info for Alpine with an os-release file', async function() {
|
|
88
|
+
fsStub.withArgs('/etc/os-release').resolves('NAME="Alpine Linux"\nID=alpine\nVERSION_ID=3.20.3\nPRETTY_NAME="Alpine Linux v3.20"\nHOME_URL="https://alpinelinux.org/"\nBUG_REPORT_URL="https://gitlab.alpinelinux.org/alpine/aports/-/issues"\n');
|
|
89
|
+
fsStub.withArgs('/usr/lib/os-release').rejects(new Error('ENOENT'));
|
|
90
|
+
fsStub.withArgs('/etc/alpine-release').resolves('3.20.3\n');
|
|
91
|
+
|
|
92
|
+
const result = await linuxOsInfo();
|
|
93
|
+
expect(result).to.deep.equal({
|
|
94
|
+
file: '/etc/os-release',
|
|
95
|
+
name: 'Alpine Linux',
|
|
96
|
+
id: 'alpine',
|
|
97
|
+
version_id: '3.20.3',
|
|
98
|
+
pretty_name: 'Alpine Linux v3.20',
|
|
99
|
+
home_url: 'https://alpinelinux.org/',
|
|
100
|
+
bug_report_url: 'https://gitlab.alpinelinux.org/alpine/aports/-/issues',
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('should return info when no os-release or alpine-release files are found', async function() {
|
|
105
|
+
fsStub.withArgs('/etc/os-release').rejects(new Error('ENOENT'));
|
|
106
|
+
fsStub.withArgs('/usr/lib/os-release').rejects(new Error('ENOENT'));
|
|
107
|
+
fsStub.withArgs('/etc/alpine-release').rejects(new Error('ENOENT'));
|
|
108
|
+
|
|
109
|
+
const result = await linuxOsInfo();
|
|
110
|
+
// no file found
|
|
111
|
+
expect(result).deep.equal({ file: undefined });
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('should return null for non-linux systems', async function() {
|
|
115
|
+
osStub.returns('Windows_NT');
|
|
116
|
+
|
|
117
|
+
const result = await linuxOsInfo();
|
|
118
|
+
|
|
119
|
+
expect(result).to.be.null;
|
|
120
|
+
});
|
|
121
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contrast/core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.42.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,12 +16,12 @@
|
|
|
16
16
|
"test": "../scripts/test.sh"
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@contrast/common": "1.
|
|
20
|
-
"@contrast/config": "1.
|
|
19
|
+
"@contrast/common": "1.27.0",
|
|
20
|
+
"@contrast/config": "1.37.0",
|
|
21
21
|
"@contrast/find-package-json": "^1.1.0",
|
|
22
22
|
"@contrast/fn-inspect": "^4.3.0",
|
|
23
|
-
"@contrast/logger": "1.
|
|
24
|
-
"@contrast/patcher": "1.
|
|
23
|
+
"@contrast/logger": "1.15.0",
|
|
24
|
+
"@contrast/patcher": "1.14.0",
|
|
25
25
|
"axios": "^1.7.4",
|
|
26
26
|
"semver": "^7.6.0"
|
|
27
27
|
}
|