@mitre/inspec-objects 0.0.31 → 0.0.34
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/.eslintignore +2 -0
- package/.eslintrc +41 -0
- package/.github/workflows/e2e-test.yml +6 -19
- package/.github/workflows/linter.yml +27 -0
- package/.github/workflows/push-to-gpr.yml +36 -0
- package/.github/workflows/push-to-npm.yml +7 -6
- package/README.md +48 -0
- package/images/Delta_Process.jpg +0 -0
- package/images/ts-inspec-objects.jpg +0 -0
- package/lib/index.d.ts +7 -7
- package/lib/objects/control.d.ts +2 -2
- package/lib/objects/control.js +40 -21
- package/lib/objects/profile.d.ts +6 -6
- package/lib/objects/profile.js +1 -1
- package/lib/parsers/json.d.ts +2 -2
- package/lib/parsers/json.js +15 -11
- package/lib/parsers/oval.js +20 -19
- package/lib/parsers/xccdf.d.ts +1 -1
- package/lib/parsers/xccdf.js +8 -5
- package/lib/utilities/diff.d.ts +3 -3
- package/lib/utilities/diff.js +20 -18
- package/lib/utilities/diffMarkdown.d.ts +1 -1
- package/lib/utilities/diffMarkdown.js +14 -18
- package/lib/utilities/global.d.ts +1 -4
- package/lib/utilities/global.js +25 -13
- package/lib/utilities/logging.d.ts +1 -1
- package/lib/utilities/update.d.ts +2 -1
- package/lib/utilities/update.js +164 -87
- package/lib/utilities/xccdf.js +0 -1
- package/package.json +7 -4
- package/tsconfig.json +20 -21
package/lib/parsers/xccdf.js
CHANGED
|
@@ -41,7 +41,7 @@ function ensureDecodedXMLStringValue(input) {
|
|
|
41
41
|
return lodash_1.default.get(input, '[0].#text') ? lodash_1.default.get(input, '[0].#text') : input;
|
|
42
42
|
}
|
|
43
43
|
// Moving the newline removal to diff library rather than processXCCDF level
|
|
44
|
-
function processXCCDF(xml, removeNewlines
|
|
44
|
+
function processXCCDF(xml, removeNewlines, useRuleId, ovalDefinitions) {
|
|
45
45
|
const parsedXML = (0, xccdf_1.convertEncodedXmlIntoJson)(xml);
|
|
46
46
|
const rules = extractAllRules(parsedXML.Benchmark[0].Group);
|
|
47
47
|
const profile = new profile_1.default({
|
|
@@ -91,13 +91,15 @@ function processXCCDF(xml, removeNewlines = false, useRuleId, ovalDefinitions) {
|
|
|
91
91
|
control.id = rule.version;
|
|
92
92
|
break;
|
|
93
93
|
case 'cis':
|
|
94
|
+
// eslint-disable-next-line no-case-declarations
|
|
94
95
|
const controlIdRegex = /\d(\d?)(\d?)(\d?)(.\d(\d?)(\d?)(\d?))?(.\d(\d?)(\d?)(\d?))?(.\d(\d?)(\d?)(\d?))?(.\d(\d?)(\d?)(\d?))?/g;
|
|
96
|
+
// eslint-disable-next-line no-case-declarations
|
|
95
97
|
const controlIdMatch = controlIdRegex.exec(rule['@_id']);
|
|
96
98
|
if (controlIdMatch) {
|
|
97
99
|
control.id = controlIdMatch[0];
|
|
98
100
|
}
|
|
99
101
|
else {
|
|
100
|
-
throw new Error(`Could not parse control ID from rule ID: ${rule['@_id']}. Expecting format: 'xccdf_org.cisecurity.benchmarks_rule_1.1.11_Rule_title_summary`);
|
|
102
|
+
throw new Error(`Could not parse control ID from rule ID: ${rule['@_id']}. Expecting something in this example format: 'xccdf_org.cisecurity.benchmarks_rule_1.1.11_Rule_title_summary`);
|
|
101
103
|
}
|
|
102
104
|
break;
|
|
103
105
|
default:
|
|
@@ -147,7 +149,7 @@ function processXCCDF(xml, removeNewlines = false, useRuleId, ovalDefinitions) {
|
|
|
147
149
|
}
|
|
148
150
|
// Very CIS specific
|
|
149
151
|
else if (rule['complex-check']) {
|
|
150
|
-
|
|
152
|
+
const checkTexts = [];
|
|
151
153
|
for (const complexChecks of rule['complex-check']) {
|
|
152
154
|
const allComplexChecks = extractAllComplexChecks(complexChecks);
|
|
153
155
|
if (control.id === '1.1.1.5') {
|
|
@@ -226,7 +228,7 @@ function processXCCDF(xml, removeNewlines = false, useRuleId, ovalDefinitions) {
|
|
|
226
228
|
control.tags.gid = rule.group['@_id'],
|
|
227
229
|
control.tags.rid = rule['@_id'];
|
|
228
230
|
control.tags.stig_id = rule['version'];
|
|
229
|
-
if (typeof rule.group.title ===
|
|
231
|
+
if (typeof rule.group.title === 'string') {
|
|
230
232
|
control.tags.gtitle = (0, xccdf_1.removeXMLSpecialCharacters)(rule.group.title);
|
|
231
233
|
}
|
|
232
234
|
else {
|
|
@@ -320,6 +322,7 @@ function processXCCDF(xml, removeNewlines = false, useRuleId, ovalDefinitions) {
|
|
|
320
322
|
if (typeof referenceText === 'string' && referenceText.indexOf('§') !== -1) {
|
|
321
323
|
const referenceParts = referenceText.split('§');
|
|
322
324
|
if (referenceParts.length == 2) {
|
|
325
|
+
// eslint-disable-next-line prefer-const
|
|
323
326
|
let [identifierType, identifier] = referenceText.split('§');
|
|
324
327
|
identifierType = identifierType.toLowerCase();
|
|
325
328
|
if (!(identifierType in control.tags)) {
|
|
@@ -333,7 +336,7 @@ function processXCCDF(xml, removeNewlines = false, useRuleId, ovalDefinitions) {
|
|
|
333
336
|
}
|
|
334
337
|
}
|
|
335
338
|
else {
|
|
336
|
-
console.warn(
|
|
339
|
+
console.warn('Reference parts of invalid length:');
|
|
337
340
|
console.log(referenceParts);
|
|
338
341
|
}
|
|
339
342
|
}
|
package/lib/utilities/diff.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import Profile from
|
|
2
|
-
import { ProfileDiff } from
|
|
3
|
-
import winston from
|
|
1
|
+
import Profile from '../objects/profile';
|
|
2
|
+
import { ProfileDiff } from '../types/diff';
|
|
3
|
+
import winston from 'winston';
|
|
4
4
|
export declare function removeNewlines(control?: Record<string, unknown>): Record<string, unknown>;
|
|
5
5
|
export declare function ignoreFormattingDiff(diffData: Record<string, unknown>): Record<string, unknown>;
|
|
6
6
|
export declare function diffProfile(fromProfile: Profile, toProfile: Profile, logger: winston.Logger): {
|
package/lib/utilities/diff.js
CHANGED
|
@@ -11,10 +11,10 @@ function removeNewlines(control) {
|
|
|
11
11
|
return {};
|
|
12
12
|
}
|
|
13
13
|
return lodash_1.default.mapValues(control, (value) => {
|
|
14
|
-
if (typeof value ===
|
|
15
|
-
return value.replace(/\n/g,
|
|
14
|
+
if (typeof value === 'string') {
|
|
15
|
+
return value.replace(/\n/g, '{{{{newlineHERE}}}}').trim();
|
|
16
16
|
}
|
|
17
|
-
else if (typeof value ===
|
|
17
|
+
else if (typeof value === 'object' && value !== null) {
|
|
18
18
|
return removeNewlines(value);
|
|
19
19
|
}
|
|
20
20
|
return value;
|
|
@@ -24,28 +24,28 @@ exports.removeNewlines = removeNewlines;
|
|
|
24
24
|
// Goal is to use a linter for the formatting and compare characters without whitespaces here
|
|
25
25
|
function ignoreFormattingDiff(diffData) {
|
|
26
26
|
return lodash_1.default.transform(diffData, (result, diffValue, key) => {
|
|
27
|
-
if (lodash_1.default.has(diffValue,
|
|
27
|
+
if (lodash_1.default.has(diffValue, '__new')) {
|
|
28
28
|
// Remove any trailing space
|
|
29
|
-
if (typeof lodash_1.default.get(diffValue,
|
|
30
|
-
typeof lodash_1.default.get(diffValue,
|
|
31
|
-
if ((0, global_1.removeWhitespace)(lodash_1.default.get(diffValue,
|
|
32
|
-
(0, global_1.removeWhitespace)(lodash_1.default.get(diffValue,
|
|
33
|
-
lodash_1.default.set(result, key, lodash_1.default.get(diffValue,
|
|
29
|
+
if (typeof lodash_1.default.get(diffValue, '__new') === 'string' &&
|
|
30
|
+
typeof lodash_1.default.get(diffValue, '__old') === 'string') {
|
|
31
|
+
if ((0, global_1.removeWhitespace)(lodash_1.default.get(diffValue, '__new')) !==
|
|
32
|
+
(0, global_1.removeWhitespace)(lodash_1.default.get(diffValue, '__old'))) {
|
|
33
|
+
lodash_1.default.set(result, key, lodash_1.default.get(diffValue, '__new'));
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
36
|
else {
|
|
37
|
-
result[key] = lodash_1.default.get(diffValue,
|
|
37
|
+
result[key] = lodash_1.default.get(diffValue, '__new');
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
40
|
else if (Array.isArray(diffValue)) {
|
|
41
41
|
result[key] = diffValue
|
|
42
|
-
.map((value) => value[0] ===
|
|
42
|
+
.map((value) => value[0] === '+' && value[1])
|
|
43
43
|
.filter((value) => value);
|
|
44
44
|
}
|
|
45
|
-
else if (typeof diffValue ===
|
|
45
|
+
else if (typeof diffValue === 'object') {
|
|
46
46
|
result[key] = ignoreFormattingDiff(diffValue);
|
|
47
47
|
}
|
|
48
|
-
else if (key.endsWith(
|
|
48
|
+
else if (key.endsWith('__deleted')) {
|
|
49
49
|
return undefined;
|
|
50
50
|
}
|
|
51
51
|
else {
|
|
@@ -77,13 +77,13 @@ function diffProfile(fromProfile, toProfile, logger) {
|
|
|
77
77
|
.sort();
|
|
78
78
|
const toControlIDs = toProfile.controls.map((control) => control.id).sort();
|
|
79
79
|
// Find new controls
|
|
80
|
-
const controlIDDiff = (_a = (0, json_diff_1.diff)(fromControlIDs, toControlIDs)) === null || _a === void 0 ? void 0 : _a.filter((item) => !(item.length === 1 && item[0] ===
|
|
80
|
+
const controlIDDiff = (_a = (0, json_diff_1.diff)(fromControlIDs, toControlIDs)) === null || _a === void 0 ? void 0 : _a.filter((item) => !(item.length === 1 && item[0] === ' '));
|
|
81
81
|
// Contains the new IDs
|
|
82
82
|
const changedControlIds = [];
|
|
83
83
|
// a diffValue has an entry for both what was subtracted ("-")
|
|
84
84
|
// and what was added ("+") -- need to handle both
|
|
85
85
|
controlIDDiff === null || controlIDDiff === void 0 ? void 0 : controlIDDiff.forEach((diffValue) => {
|
|
86
|
-
if (diffValue[0] ===
|
|
86
|
+
if (diffValue[0] === '-') {
|
|
87
87
|
const existingControl = fromProfile.controls.find((control) => control.id === diffValue[1]);
|
|
88
88
|
// Check if the control has been given a new ID
|
|
89
89
|
if (existingControl) {
|
|
@@ -92,7 +92,7 @@ function diffProfile(fromProfile, toProfile, logger) {
|
|
|
92
92
|
profileDiff.renamedControlIDs[existingControl.id] = newControl.id;
|
|
93
93
|
originalDiff.renamedControlIDs[existingControl.id] = newControl.id;
|
|
94
94
|
changedControlIds.push(newControl.id.toLowerCase());
|
|
95
|
-
const controlDiff = lodash_1.default.omit((0, json_diff_1.diff)(existingControl, newControl),
|
|
95
|
+
const controlDiff = lodash_1.default.omit((0, json_diff_1.diff)(existingControl, newControl), 'code__deleted');
|
|
96
96
|
// logger.info("CONTROL DIFF:" + JSON.stringify(controlDiff, null, 2))
|
|
97
97
|
const renamedControlIgnoredFormatting = ignoreFormattingDiff(controlDiff);
|
|
98
98
|
logger.info(JSON.stringify(renamedControlIgnoredFormatting));
|
|
@@ -111,7 +111,7 @@ function diffProfile(fromProfile, toProfile, logger) {
|
|
|
111
111
|
logger.error(`Unable to find existing control ${diffValue[1]}`);
|
|
112
112
|
}
|
|
113
113
|
}
|
|
114
|
-
else if (diffValue[0] ===
|
|
114
|
+
else if (diffValue[0] === '+' && !changedControlIds.includes(diffValue[1].toLowerCase()) && diffValue[1]) {
|
|
115
115
|
logger.info(JSON.stringify(diffValue));
|
|
116
116
|
logger.info(JSON.stringify(changedControlIds));
|
|
117
117
|
profileDiff.addedControlIDs.push(diffValue[1]);
|
|
@@ -134,10 +134,12 @@ function diffProfile(fromProfile, toProfile, logger) {
|
|
|
134
134
|
for (const fromControl of fromProfile.controls) {
|
|
135
135
|
const toControl = toProfile.controls.find((control) => control.id === fromControl.id);
|
|
136
136
|
if (toControl) {
|
|
137
|
-
const controlDiff = lodash_1.default.omit((0, json_diff_1.diff)(fromControl, toControl),
|
|
137
|
+
const controlDiff = lodash_1.default.omit((0, json_diff_1.diff)(fromControl, toControl), 'code__deleted');
|
|
138
138
|
if (controlDiff) {
|
|
139
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
139
140
|
profileDiff.changedControls[toControl.id] = ignoreFormattingDiff(controlDiff);
|
|
140
141
|
profileDiff.changedControlIDs.push(toControl.id);
|
|
142
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
141
143
|
originalDiff.changedControls[toControl.id] = controlDiff;
|
|
142
144
|
originalDiff.changedControlIDs.push(toControl.id);
|
|
143
145
|
}
|
|
@@ -5,10 +5,6 @@ const tslib_1 = require("tslib");
|
|
|
5
5
|
const mustache_1 = tslib_1.__importDefault(require("mustache"));
|
|
6
6
|
const lodash_1 = tslib_1.__importDefault(require("lodash"));
|
|
7
7
|
const automatticUpdateTemplate_json_1 = tslib_1.__importDefault(require("../resources/automatticUpdateTemplate.json"));
|
|
8
|
-
function getUpdatedCheckForId(id, profile) {
|
|
9
|
-
const foundControl = profile.controls.find((control) => control.id === id);
|
|
10
|
-
return lodash_1.default.get(foundControl === null || foundControl === void 0 ? void 0 : foundControl.descs, "check") || "Missing check";
|
|
11
|
-
}
|
|
12
8
|
function createDiffMarkdown(diff) {
|
|
13
9
|
const renderableDiffData = {
|
|
14
10
|
addedControls: Object.values(diff.ignoreFormattingDiff.addedControls),
|
|
@@ -30,10 +26,10 @@ function createDiffMarkdown(diff) {
|
|
|
30
26
|
Object.entries(diff.rawDiff.changedControls).forEach(([id, controlDiff]) => {
|
|
31
27
|
var _a, _b;
|
|
32
28
|
if ((_a = controlDiff.descs) === null || _a === void 0 ? void 0 : _a.check) {
|
|
33
|
-
const oldCheck = lodash_1.default.get(controlDiff.descs.check,
|
|
34
|
-
const newCheck = lodash_1.default.get(controlDiff.descs.check,
|
|
35
|
-
if (oldCheck.replace(/\n/g,
|
|
36
|
-
newCheck.replace(/\n/g,
|
|
29
|
+
const oldCheck = lodash_1.default.get(controlDiff.descs.check, '__old');
|
|
30
|
+
const newCheck = lodash_1.default.get(controlDiff.descs.check, '__new');
|
|
31
|
+
if (oldCheck.replace(/\n/g, '').replace(/\W/g, '') !==
|
|
32
|
+
newCheck.replace(/\n/g, '').replace(/\W/g, '')) {
|
|
37
33
|
renderableDiffData.updatedChecks.push({
|
|
38
34
|
id: id,
|
|
39
35
|
old: oldCheck,
|
|
@@ -42,10 +38,10 @@ function createDiffMarkdown(diff) {
|
|
|
42
38
|
}
|
|
43
39
|
}
|
|
44
40
|
if ((_b = controlDiff.descs) === null || _b === void 0 ? void 0 : _b.fix) {
|
|
45
|
-
const oldFix = lodash_1.default.get(controlDiff.descs.fix,
|
|
46
|
-
const newFix = lodash_1.default.get(controlDiff.descs.fix,
|
|
47
|
-
if (oldFix.replace(/\n/g,
|
|
48
|
-
newFix.replace(/\n/g,
|
|
41
|
+
const oldFix = lodash_1.default.get(controlDiff.descs.fix, '__old');
|
|
42
|
+
const newFix = lodash_1.default.get(controlDiff.descs.fix, '__new');
|
|
43
|
+
if (oldFix.replace(/\n/g, '').replace(/\W/g, '') !==
|
|
44
|
+
newFix.replace(/\n/g, '').replace(/\W/g, '')) {
|
|
49
45
|
renderableDiffData.updatedFixes.push({
|
|
50
46
|
id: id,
|
|
51
47
|
old: oldFix,
|
|
@@ -54,8 +50,8 @@ function createDiffMarkdown(diff) {
|
|
|
54
50
|
}
|
|
55
51
|
}
|
|
56
52
|
if (controlDiff.impact) {
|
|
57
|
-
const oldImpact = lodash_1.default.get(controlDiff.impact,
|
|
58
|
-
const newImpact = lodash_1.default.get(controlDiff.impact,
|
|
53
|
+
const oldImpact = lodash_1.default.get(controlDiff.impact, '__old');
|
|
54
|
+
const newImpact = lodash_1.default.get(controlDiff.impact, '__new');
|
|
59
55
|
if (oldImpact !== newImpact) {
|
|
60
56
|
renderableDiffData.updatedImpacts.push({
|
|
61
57
|
id: id,
|
|
@@ -65,8 +61,8 @@ function createDiffMarkdown(diff) {
|
|
|
65
61
|
}
|
|
66
62
|
}
|
|
67
63
|
if (controlDiff.title) {
|
|
68
|
-
const oldTitle = lodash_1.default.get(controlDiff.title,
|
|
69
|
-
const newTitle = lodash_1.default.get(controlDiff.title,
|
|
64
|
+
const oldTitle = lodash_1.default.get(controlDiff.title, '__old');
|
|
65
|
+
const newTitle = lodash_1.default.get(controlDiff.title, '__new');
|
|
70
66
|
if (oldTitle !== newTitle) {
|
|
71
67
|
renderableDiffData.updatedTitles.push({
|
|
72
68
|
id: id,
|
|
@@ -76,8 +72,8 @@ function createDiffMarkdown(diff) {
|
|
|
76
72
|
}
|
|
77
73
|
}
|
|
78
74
|
if (controlDiff.desc) {
|
|
79
|
-
const oldDesc = lodash_1.default.get(controlDiff.desc,
|
|
80
|
-
const newDesc = lodash_1.default.get(controlDiff.desc,
|
|
75
|
+
const oldDesc = lodash_1.default.get(controlDiff.desc, '__old');
|
|
76
|
+
const newDesc = lodash_1.default.get(controlDiff.desc, '__new');
|
|
81
77
|
if (oldDesc !== newDesc) {
|
|
82
78
|
renderableDiffData.updatedDescriptions.push({
|
|
83
79
|
id: id,
|
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
export declare function wrap(s: string, lineLength?: number): string;
|
|
2
2
|
export declare function unformatText(s: string): string;
|
|
3
3
|
export declare function removeWhitespace(input: string): string;
|
|
4
|
-
declare
|
|
5
|
-
declare const escapeDoubleQuotes: (s: string) => string;
|
|
6
|
-
declare const wrapAndEscapeQuotes: (s: string, lineLength?: number) => string;
|
|
7
|
-
export { escapeQuotes, escapeDoubleQuotes, wrapAndEscapeQuotes };
|
|
4
|
+
export declare function escapeQuotes(s: string): string;
|
|
8
5
|
export declare function removeNewlinePlaceholders(s: string): string;
|
|
9
6
|
export declare function getFirstPath(object: Record<string, unknown>, paths: string[]): string;
|
|
10
7
|
export declare function hasPath(file: Record<string, unknown>, path: string | string[]): boolean;
|
package/lib/utilities/global.js
CHANGED
|
@@ -1,23 +1,22 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.hasPath = exports.getFirstPath = exports.removeNewlinePlaceholders = exports.
|
|
3
|
+
exports.hasPath = exports.getFirstPath = exports.removeNewlinePlaceholders = exports.escapeQuotes = exports.removeWhitespace = exports.unformatText = exports.wrap = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
const lodash_1 = tslib_1.__importDefault(require("lodash"));
|
|
6
6
|
// Breaks lines down to lineLength number of characters
|
|
7
7
|
function wrap(s, lineLength = 80) {
|
|
8
|
-
let newString =
|
|
9
|
-
let currentLine = "";
|
|
8
|
+
let newString = '';
|
|
10
9
|
let currentLength = 0;
|
|
11
10
|
let shouldBreakLine = false;
|
|
12
|
-
for (
|
|
11
|
+
for (let i = 0; i < s.length; i++) {
|
|
13
12
|
if (shouldBreakLine) {
|
|
14
|
-
newString +=
|
|
13
|
+
newString += '\n';
|
|
15
14
|
currentLength = 0;
|
|
16
15
|
shouldBreakLine = false;
|
|
17
16
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
if (nextChar ===
|
|
17
|
+
const currentChar = s.charAt(i);
|
|
18
|
+
const nextChar = s.charAt(i + 1);
|
|
19
|
+
if (nextChar === ' ') {
|
|
21
20
|
if (currentLength >= lineLength) {
|
|
22
21
|
shouldBreakLine = true;
|
|
23
22
|
newString += currentChar;
|
|
@@ -36,6 +35,7 @@ function wrap(s, lineLength = 80) {
|
|
|
36
35
|
return newString;
|
|
37
36
|
}
|
|
38
37
|
exports.wrap = wrap;
|
|
38
|
+
// Remove new lines and tabs
|
|
39
39
|
function unformatText(s) {
|
|
40
40
|
return s.replace(/\n/g, ' ').replace(/\\n/g, ' ').replace(/( +|\t)/g, ' ');
|
|
41
41
|
}
|
|
@@ -44,12 +44,24 @@ function removeWhitespace(input) {
|
|
|
44
44
|
return input.replace(/\s/gi, '');
|
|
45
45
|
}
|
|
46
46
|
exports.removeWhitespace = removeWhitespace;
|
|
47
|
-
const
|
|
47
|
+
const escapeSingleQuotes = (s) => {
|
|
48
|
+
return s.replace(/\\/g, '\\\\').replace(/'/g, "\\'"); // Escape backslashes and quotes
|
|
49
|
+
};
|
|
50
|
+
const escapeDoubleQuotes = (s) => {
|
|
51
|
+
return s.replace(/\\/g, '\\\\').replace(/"/g, '\\"'); // Escape backslashes and double quotes
|
|
52
|
+
};
|
|
53
|
+
function escapeQuotes(s) {
|
|
54
|
+
if (s.includes("'") && s.includes('"')) {
|
|
55
|
+
return `%q(${removeNewlinePlaceholders(s)})`;
|
|
56
|
+
}
|
|
57
|
+
else if (s.includes("'")) {
|
|
58
|
+
return `"${escapeDoubleQuotes(removeNewlinePlaceholders(s))}"`;
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
return `'${escapeSingleQuotes(removeNewlinePlaceholders(s))}'`;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
48
64
|
exports.escapeQuotes = escapeQuotes;
|
|
49
|
-
const escapeDoubleQuotes = (s) => s.replace(/\\/g, "\\\\").replace(/"/g, '\\"'); // Escape backslashes and double quotes
|
|
50
|
-
exports.escapeDoubleQuotes = escapeDoubleQuotes;
|
|
51
|
-
const wrapAndEscapeQuotes = (s, lineLength) => escapeDoubleQuotes(wrap(s, lineLength)); // Escape backslashes and quotes, and wrap long lines
|
|
52
|
-
exports.wrapAndEscapeQuotes = wrapAndEscapeQuotes;
|
|
53
65
|
function removeNewlinePlaceholders(s) {
|
|
54
66
|
return s.replace(/\{\{\{\{newlineHERE\}\}\}\}/g, '\n');
|
|
55
67
|
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import winston from
|
|
1
|
+
import winston from 'winston';
|
|
2
2
|
export declare function createWinstonLogger(level?: string): winston.Logger;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import winston from
|
|
1
|
+
import winston from 'winston';
|
|
2
2
|
import Control from '../objects/control';
|
|
3
3
|
import Profile from '../objects/profile';
|
|
4
4
|
import { ProfileDiff } from '../types/diff';
|
|
@@ -11,6 +11,7 @@ export declare type UpdatedProfileReturn = {
|
|
|
11
11
|
};
|
|
12
12
|
markdown: string;
|
|
13
13
|
};
|
|
14
|
+
export declare function getExistingDescribeFromControl(control: Control): string;
|
|
14
15
|
export declare function findUpdatedControlByAllIdentifiers(existingControl: Control, updatedControls: Control[]): Control | undefined;
|
|
15
16
|
export declare function updateControl(from: Control, update: Partial<Control>, logger: winston.Logger): Control;
|
|
16
17
|
export declare function updateProfile(from: Profile, using: Profile, logger: winston.Logger): Omit<UpdatedProfileReturn, 'markdown'>;
|
package/lib/utilities/update.js
CHANGED
|
@@ -2,14 +2,13 @@
|
|
|
2
2
|
// Utilities to update a profile or control with new metadata
|
|
3
3
|
// The ultimate goal is to preserve all the metadata that is already there and only add what is new
|
|
4
4
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
-
exports.updateProfileUsingXCCDF = exports.updateProfile = exports.updateControl = exports.findUpdatedControlByAllIdentifiers = void 0;
|
|
5
|
+
exports.updateProfileUsingXCCDF = exports.updateProfile = exports.updateControl = exports.findUpdatedControlByAllIdentifiers = exports.getExistingDescribeFromControl = void 0;
|
|
6
6
|
const tslib_1 = require("tslib");
|
|
7
7
|
const lodash_1 = tslib_1.__importDefault(require("lodash"));
|
|
8
8
|
const profile_1 = tslib_1.__importDefault(require("../objects/profile"));
|
|
9
9
|
const xccdf_1 = require("../parsers/xccdf");
|
|
10
10
|
const diff_1 = require("./diff");
|
|
11
11
|
const diffMarkdown_1 = require("./diffMarkdown");
|
|
12
|
-
const knownInSpecKeywords = ['title', 'desc', 'impact', 'ref', 'tag', "\""];
|
|
13
12
|
function projectValuesOntoExistingObj(dst, src, currentPath = '') {
|
|
14
13
|
for (const updatedValue in src) {
|
|
15
14
|
const existingValue = lodash_1.default.get(dst, updatedValue);
|
|
@@ -32,101 +31,179 @@ function projectValuesOntoExistingObj(dst, src, currentPath = '') {
|
|
|
32
31
|
}
|
|
33
32
|
return dst;
|
|
34
33
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
34
|
+
function getRangesForLines(text) {
|
|
35
|
+
/*
|
|
36
|
+
Returns an array containing two numerical indices (i.e., start and stop
|
|
37
|
+
line numbers) for each string or multi-line comment, given raw text as
|
|
38
|
+
an input parameter. The raw text is a string containing the entirety of an
|
|
39
|
+
InSpec control.
|
|
40
|
+
|
|
41
|
+
Algorithm utilizes a pair of stacks (i.e., `stack`, `rangeStack`) to keep
|
|
42
|
+
track of string delimiters and their associated line numbers, respectively.
|
|
43
|
+
|
|
44
|
+
Combinations Handled:
|
|
45
|
+
- Single quotes (')
|
|
46
|
+
- Double quotes (")
|
|
47
|
+
- Back ticks (`)
|
|
48
|
+
- Mixed quotes ("`'")
|
|
49
|
+
- Percent strings (%; keys: q, Q, r, i, I, w, W, x; delimiters: (), {},
|
|
50
|
+
[], <>, most non-alphanumeric characters); (e.g., "%q()")
|
|
51
|
+
- Percent literals (%; delimiters: (), {}, [], <>, most non-
|
|
52
|
+
alphanumeric characters); (e.g., "%()")
|
|
53
|
+
- Multi-line comments (e.g., =begin\nSome comment\n=end)
|
|
54
|
+
- Variable delimiters (i.e., paranthesis: (); array: []; hash: {})
|
|
55
|
+
*/
|
|
56
|
+
const stringDelimiters = { '(': ')', '{': '}', '[': ']', '<': '>' };
|
|
57
|
+
const variableDelimiters = { '(': ')', '{': '}', '[': ']' };
|
|
58
|
+
const quotes = '\'"`';
|
|
59
|
+
const strings = 'qQriIwWxs';
|
|
60
|
+
let skipCharLength;
|
|
61
|
+
(function (skipCharLength) {
|
|
62
|
+
skipCharLength[skipCharLength["string"] = '('.length] = "string";
|
|
63
|
+
skipCharLength[skipCharLength["percentString"] = 'q('.length] = "percentString";
|
|
64
|
+
skipCharLength[skipCharLength["commentBegin"] = '=begin'.length] = "commentBegin";
|
|
65
|
+
})(skipCharLength || (skipCharLength = {}));
|
|
66
|
+
const stack = [];
|
|
67
|
+
const rangeStack = [];
|
|
68
|
+
const ranges = [];
|
|
69
|
+
const lines = text.split('\n');
|
|
70
|
+
for (let i = 0; i < lines.length; i++) {
|
|
71
|
+
let j = 0;
|
|
72
|
+
while (j < lines[i].length) {
|
|
73
|
+
const line = lines[i];
|
|
74
|
+
let char = line[j];
|
|
75
|
+
const isEmptyStack = (stack.length == 0);
|
|
76
|
+
const isNotEmptyStack = (stack.length > 0);
|
|
77
|
+
const isQuoteChar = quotes.includes(char);
|
|
78
|
+
const isNotEscapeChar = ((j == 0) || (j > 0 && line[j - 1] != '\\'));
|
|
79
|
+
const isPercentChar = (char == '%');
|
|
80
|
+
const isVariableDelimiterChar = Object.keys(variableDelimiters).includes(char);
|
|
81
|
+
const isStringDelimiterChar = ((j < line.length - 1) && (/^[^A-Za-z0-9]$/.test(line[j + 1])));
|
|
82
|
+
const isCommentBeginChar = ((j == 0) && (line.length >= 6) && (line.slice(0, 6) == '=begin'));
|
|
83
|
+
const isPercentStringKeyChar = ((j < line.length - 1) && (strings.includes(line[j + 1])));
|
|
84
|
+
const isPercentStringDelimiterChar = ((j < line.length - 2) && (/^[^A-Za-z0-9]$/.test(line[j + 2])));
|
|
85
|
+
const isPercentString = (isPercentStringKeyChar && isPercentStringDelimiterChar);
|
|
86
|
+
let baseCondition = (isEmptyStack && isNotEscapeChar);
|
|
87
|
+
const quotePushCondition = (baseCondition && isQuoteChar);
|
|
88
|
+
const variablePushCondition = (baseCondition && isVariableDelimiterChar);
|
|
89
|
+
const stringPushCondition = (baseCondition && isPercentChar && isStringDelimiterChar);
|
|
90
|
+
const percentStringPushCondition = (baseCondition && isPercentChar && isPercentString);
|
|
91
|
+
const commentBeginCondition = (baseCondition && isCommentBeginChar);
|
|
92
|
+
if (stringPushCondition) {
|
|
93
|
+
j += skipCharLength.string; // j += 1
|
|
55
94
|
}
|
|
56
|
-
else {
|
|
57
|
-
|
|
58
|
-
indentedMetadataOverride = false;
|
|
95
|
+
else if (percentStringPushCondition) {
|
|
96
|
+
j += skipCharLength.percentString; // j += 2
|
|
59
97
|
}
|
|
60
|
-
if (
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
98
|
+
else if (commentBeginCondition) {
|
|
99
|
+
j += skipCharLength.commentBegin; // j += 6
|
|
100
|
+
}
|
|
101
|
+
char = line[j];
|
|
102
|
+
baseCondition = (isNotEmptyStack && isNotEscapeChar);
|
|
103
|
+
const delimiterCondition = (baseCondition && Object.keys(stringDelimiters).includes(stack[stack.length - 1]));
|
|
104
|
+
const delimiterPushCondition = (delimiterCondition && (stack[stack.length - 1] == char));
|
|
105
|
+
const delimiterPopCondition = (delimiterCondition && (stringDelimiters[stack[stack.length - 1]] == char));
|
|
106
|
+
const basePopCondition = (baseCondition && (stack[stack.length - 1] == char) && !Object.keys(stringDelimiters).includes(char));
|
|
107
|
+
const isCommentEndChar = ((j == 0) && (line.length >= 4) && (line.slice(0, 4) == '=end'));
|
|
108
|
+
const commentEndCondition = (baseCondition && isCommentEndChar && (stack[stack.length - 1] == '=begin'));
|
|
109
|
+
const popCondition = (basePopCondition || delimiterPopCondition || commentEndCondition);
|
|
110
|
+
const pushCondition = (quotePushCondition || variablePushCondition || stringPushCondition ||
|
|
111
|
+
percentStringPushCondition || delimiterPushCondition || commentBeginCondition);
|
|
112
|
+
if (popCondition) {
|
|
113
|
+
stack.pop();
|
|
114
|
+
rangeStack[rangeStack.length - 1].push(i);
|
|
115
|
+
const range_ = rangeStack.pop();
|
|
116
|
+
if (rangeStack.length == 0) {
|
|
117
|
+
ranges.push(range_);
|
|
71
118
|
}
|
|
72
119
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
if (percentBlockMatch && inPercentBlock === false) {
|
|
77
|
-
inPercentBlock = true;
|
|
78
|
-
lDelimiter = percentBlockMatch.groups.lDelimiter || '(';
|
|
79
|
-
switch (lDelimiter) {
|
|
80
|
-
case '{': {
|
|
81
|
-
rDelimiter = '}';
|
|
82
|
-
break;
|
|
83
|
-
}
|
|
84
|
-
case '[': {
|
|
85
|
-
rDelimiter = ']';
|
|
86
|
-
break;
|
|
87
|
-
}
|
|
88
|
-
case '<': {
|
|
89
|
-
rDelimiter = '>';
|
|
90
|
-
break;
|
|
91
|
-
}
|
|
92
|
-
default: {
|
|
93
|
-
break;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
120
|
+
else if (pushCondition) {
|
|
121
|
+
if (commentBeginCondition) {
|
|
122
|
+
stack.push('=begin');
|
|
96
123
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
124
|
+
else {
|
|
125
|
+
stack.push(char);
|
|
126
|
+
}
|
|
127
|
+
rangeStack.push([i]);
|
|
128
|
+
}
|
|
129
|
+
j++;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return ranges;
|
|
133
|
+
}
|
|
134
|
+
function joinMultiLineStringsFromRanges(text, ranges) {
|
|
135
|
+
/*
|
|
136
|
+
Returns an array of strings and joined strings at specified ranges, given
|
|
137
|
+
raw text as an input parameter.
|
|
138
|
+
*/
|
|
139
|
+
const originalLines = text.split('\n');
|
|
140
|
+
const joinedLines = [];
|
|
141
|
+
let i = 0;
|
|
142
|
+
while (i < originalLines.length) {
|
|
143
|
+
let foundInRanges = false;
|
|
144
|
+
for (const [startIndex, stopIndex] of ranges) {
|
|
145
|
+
if (i >= startIndex && i <= stopIndex) {
|
|
146
|
+
joinedLines.push(originalLines.slice(startIndex, stopIndex + 1).join('\n'));
|
|
147
|
+
foundInRanges = true;
|
|
148
|
+
i = stopIndex;
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
if (!foundInRanges) {
|
|
153
|
+
joinedLines.push(originalLines[i]);
|
|
154
|
+
}
|
|
155
|
+
i++;
|
|
156
|
+
}
|
|
157
|
+
return joinedLines;
|
|
158
|
+
}
|
|
159
|
+
function getMultiLineRanges(ranges) {
|
|
160
|
+
/*
|
|
161
|
+
Drops ranges with the same start and stop line numbers (i.e., strings
|
|
162
|
+
that populate a single line)
|
|
163
|
+
*/
|
|
164
|
+
const multiLineRanges = [];
|
|
165
|
+
for (const [start, stop] of ranges) {
|
|
166
|
+
if (start !== stop) {
|
|
167
|
+
multiLineRanges.push([start, stop]);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return multiLineRanges;
|
|
171
|
+
}
|
|
172
|
+
/*
|
|
173
|
+
This is the most likely thing to break if you are getting code formatting issues.
|
|
174
|
+
Extract the existing describe blocks (what is actually run by inspec for validation)
|
|
175
|
+
*/
|
|
176
|
+
function getExistingDescribeFromControl(control) {
|
|
177
|
+
if (control.code) {
|
|
178
|
+
// Join multi-line strings in InSpec control.
|
|
179
|
+
const ranges = getRangesForLines(control.code);
|
|
180
|
+
const multiLineRanges = getMultiLineRanges(ranges);
|
|
181
|
+
const lines = joinMultiLineStringsFromRanges(control.code, multiLineRanges); // Array of lines representing the full InSpec control, with multi-line strings collapsed
|
|
182
|
+
// Define RegExp for lines to skip when searching for describe block.
|
|
183
|
+
const skip = ['control\\W', ' title\\W', ' desc\\W', ' impact\\W', ' tag\\W', ' ref\\W'];
|
|
184
|
+
const skipRegExp = RegExp(skip.map(x => `(^${x})`).join('|'));
|
|
185
|
+
// Extract describe block from InSpec control with collapsed multiline strings.
|
|
186
|
+
const describeBlock = [];
|
|
187
|
+
let ignoreNewLine = true;
|
|
188
|
+
for (const line of lines) {
|
|
189
|
+
const checkRegExp = ((line.trim() !== '') && !skipRegExp.test(line));
|
|
190
|
+
const checkNewLine = ((line.trim() === '') && !ignoreNewLine);
|
|
191
|
+
// Include '\n' if it is part of describe block, otherwise skip line.
|
|
192
|
+
if (checkRegExp || checkNewLine) {
|
|
193
|
+
describeBlock.push(line);
|
|
194
|
+
ignoreNewLine = false;
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
ignoreNewLine = true;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return describeBlock.slice(0, describeBlock.length - 2).join('\n'); // Drop trailing ['end', '\n'] from Control block.
|
|
125
201
|
}
|
|
126
202
|
else {
|
|
127
203
|
return '';
|
|
128
204
|
}
|
|
129
205
|
}
|
|
206
|
+
exports.getExistingDescribeFromControl = getExistingDescribeFromControl;
|
|
130
207
|
function findUpdatedControlByAllIdentifiers(existingControl, updatedControls) {
|
|
131
208
|
// Try to match based on IDs
|
|
132
209
|
let updatedControl = updatedControls.find((updatedControl) => {
|