@mitre/inspec-objects 0.0.1 → 0.0.2
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/.github/workflows/e2e-test.yml +38 -0
- package/error.log +12 -0
- package/lib/objects/control.d.ts +7 -3
- package/lib/objects/control.js +8 -1
- package/lib/parsers/oval.d.ts +2 -0
- package/lib/parsers/oval.js +17 -0
- package/lib/parsers/xccdf.d.ts +7 -1
- package/lib/parsers/xccdf.js +198 -50
- package/lib/utilities/diff.d.ts +2 -0
- package/lib/utilities/diff.js +7 -3
- package/lib/utilities/global.d.ts +2 -0
- package/lib/utilities/global.js +24 -1
- package/lib/utilities/xccdf.d.ts +1 -1
- package/lib/utilities/xccdf.js +5 -2
- package/mitre-inspec-objects-v0.0.1.tgz +0 -0
- package/package-lock.json +219 -21
- package/package.json +2 -2
- package/src/objects/control.ts +15 -4
- package/src/parsers/oval.ts +18 -0
- package/src/parsers/xccdf.ts +200 -52
- package/src/types/oval.d.ts +609 -0
- package/src/types/xccdf.d.ts +830 -73
- package/src/utilities/diff.ts +9 -3
- package/src/utilities/global.ts +29 -0
- package/src/utilities/xccdf.ts +8 -3
- package/tsconfig.json +2 -1
- package/types/ionchannelAnalysis.d.ts +238 -0
- package/types/ionchannelProjects.d.ts +72 -0
- package/types/ionchannelTeams.d.ts +26 -0
- package/types/reverseMappedXCCDF.d.ts +67 -0
- package/types/splunk-sdk-no-env/index.d.ts +88 -0
package/src/parsers/xccdf.ts
CHANGED
|
@@ -1,69 +1,217 @@
|
|
|
1
1
|
import {data as CCINistMappings} from '@mitre/hdf-converters/lib/src/mappings/CciNistMappingData'
|
|
2
2
|
import Profile from '../objects/profile';
|
|
3
3
|
import { convertEncodedHTMLIntoJson, convertEncodedXmlIntoJson, impactNumberToSeverityString, severityStringToImpact } from '../utilities/xccdf';
|
|
4
|
-
import { DecodedDescription,
|
|
4
|
+
import { BenchmarkGroup, BenchmarkRule, DecodedDescription, FrontMatter, Notice, ParsedXCCDF, RationaleElement } from '../types/xccdf';
|
|
5
5
|
import Control from '../objects/control';
|
|
6
6
|
import _ from 'lodash';
|
|
7
|
+
import { OvalDefinitionValue } from '../types/oval';
|
|
8
|
+
import { CciNistMappingData } from '@mitre/hdf-converters';
|
|
7
9
|
|
|
8
|
-
export
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
export type GroupContextualizedRule = BenchmarkRule & {group: Omit<BenchmarkGroup, 'Rule' | 'Group'>}
|
|
11
|
+
|
|
12
|
+
export function extractAllRules(groups: BenchmarkGroup[]): GroupContextualizedRule[] {
|
|
13
|
+
const rules: GroupContextualizedRule[] = [];
|
|
14
|
+
groups.forEach((group) => {
|
|
15
|
+
if (group.Rule) {
|
|
16
|
+
rules.push(...(group.Rule.map((rule) => {
|
|
17
|
+
return {
|
|
18
|
+
...rule,
|
|
19
|
+
group: _.omit(group, ['Rule', 'Group'])
|
|
20
|
+
}
|
|
21
|
+
})))
|
|
22
|
+
}
|
|
23
|
+
if (group.Group) {
|
|
24
|
+
rules.push(...extractAllRules(group.Group))
|
|
25
|
+
}
|
|
26
|
+
})
|
|
27
|
+
return rules
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function processXCCDF(xml: string, ovalDefinitions?: Record<string, OvalDefinitionValue>): Profile {
|
|
31
|
+
const parsedXML: ParsedXCCDF = convertEncodedXmlIntoJson(xml)
|
|
32
|
+
const rules = extractAllRules(parsedXML.Benchmark[0].Group)
|
|
11
33
|
|
|
12
34
|
const profile = new Profile({
|
|
13
|
-
name: parsedXML.Benchmark['@_id'],
|
|
14
|
-
title: parsedXML.Benchmark.title,
|
|
15
|
-
summary: parsedXML.Benchmark.description
|
|
35
|
+
name: parsedXML.Benchmark[0]['@_id'],
|
|
36
|
+
title: (parsedXML.Benchmark[0].title[0] as FrontMatter)['#text'],
|
|
37
|
+
summary: (parsedXML.Benchmark[0].description[0] as RationaleElement)['#text']
|
|
16
38
|
});
|
|
17
39
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
40
|
+
rules.forEach(rule => {
|
|
41
|
+
let extractedDescription: string | DecodedDescription;
|
|
42
|
+
if (Array.isArray(rule.description)) {
|
|
43
|
+
extractedDescription = rule.description[0]['#text']
|
|
44
|
+
} else {
|
|
45
|
+
extractedDescription = convertEncodedHTMLIntoJson(rule.description)
|
|
46
|
+
}
|
|
47
|
+
const control = new Control();
|
|
48
|
+
|
|
49
|
+
control.id = rule.group['@_id']
|
|
50
|
+
control.title = rule['@_severity'] ? rule.title : `[[[MISSING SEVERITY FROM STIG]]] ${rule.title}`
|
|
51
|
+
control.desc = typeof extractedDescription === 'string' ? extractedDescription : extractedDescription.VulnDiscussion?.split('Satisfies: ')[0]
|
|
52
|
+
control.impact = severityStringToImpact(rule['@_severity'] || 'critical', rule.group['@_id'])
|
|
53
|
+
|
|
54
|
+
if (!control.descs || Array.isArray(control.descs)) {
|
|
55
|
+
control.descs = {}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (rule.check) {
|
|
59
|
+
if (rule.check.some((ruleValue) => 'check-content' in ruleValue)) {
|
|
60
|
+
control.descs.check = rule.check ? rule.check[0]['check-content'] : 'Missing description'
|
|
61
|
+
} else if (rule.check.some((ruleValue) => 'check-content-ref' in ruleValue) && ovalDefinitions) {
|
|
62
|
+
let referenceID: string | null = null;
|
|
63
|
+
for (const checkContent of rule.check) {
|
|
64
|
+
if ('check-content-ref' in checkContent && checkContent['@_system'].includes('oval')) {
|
|
65
|
+
for (const checkContentRef of checkContent['check-content-ref']) {
|
|
66
|
+
if (checkContentRef['@_name']) {
|
|
67
|
+
referenceID = checkContentRef['@_name']
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (referenceID && referenceID in ovalDefinitions) {
|
|
73
|
+
control.descs.check = ovalDefinitions[referenceID].metadata[0].title
|
|
74
|
+
} else if (referenceID ) {
|
|
75
|
+
console.warn(`Could not find OVAL definition for ${referenceID}`)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
control.descs.fix = rule.fixtext ? rule.fixtext[0]['#text'] : (rule.fix ? (rule.fix[0] as Notice)['#text'] || 'Missing fix text' : 'Missing fix text')
|
|
81
|
+
control.tags.severity = impactNumberToSeverityString(severityStringToImpact(rule['@_severity'] || 'critical', control.id))
|
|
82
|
+
control.tags.gid = rule.group['@_id'],
|
|
83
|
+
control.tags.rid = rule['@_id']
|
|
84
|
+
control.tags.stig_id = rule['version']
|
|
85
|
+
|
|
86
|
+
if (typeof rule.group.title === "string") {
|
|
87
|
+
control.tags.gtitle = rule.group.title
|
|
88
|
+
} else {
|
|
89
|
+
control.tags.gtitle = _.get(rule.group, 'title[0].#text')
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (rule['fix'] && rule['fix'].length > 0) {
|
|
93
|
+
control.tags.fix_id = rule['fix'][0]['@_id']
|
|
94
|
+
} else {
|
|
95
|
+
control.tags.fix_id = null
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (rule['rationale']) {
|
|
99
|
+
control.tags.rationale = rule['rationale'][0]['#text']
|
|
100
|
+
} else {
|
|
101
|
+
control.tags.rationale = null
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (typeof extractedDescription === 'object') {
|
|
105
|
+
control.tags.satisfies = extractedDescription.VulnDiscussion?.includes('Satisfies: ') && extractedDescription.VulnDiscussion.split('Satisfies: ').length >= 1 ? extractedDescription.VulnDiscussion.split('Satisfies: ')[1].split(',').map(satisfaction => satisfaction.trim()) : undefined
|
|
106
|
+
control.tags.false_negatives = extractedDescription.FalseNegatives || undefined
|
|
107
|
+
control.tags.false_positives = extractedDescription.FalsePositives || undefined
|
|
108
|
+
control.tags.documentable = typeof extractedDescription.Documentable === 'boolean' ? extractedDescription.Documentable : undefined
|
|
109
|
+
control.tags.mitigations = extractedDescription.Mitigations || undefined
|
|
110
|
+
control.tags.severity_override_guidance = extractedDescription.SeverityOverrideGuidance || undefined
|
|
111
|
+
control.tags.potential_impacts = extractedDescription.PotentialImpacts || undefined
|
|
112
|
+
control.tags.third_party_tools = extractedDescription.ThirdPartyTools || undefined
|
|
113
|
+
control.tags.mitigation_control = extractedDescription.MitigationControl || undefined
|
|
114
|
+
control.tags.mitigation_controls = extractedDescription.MitigationControls || undefined
|
|
115
|
+
control.tags.responsibility = extractedDescription.Responsibility || undefined
|
|
116
|
+
control.tags.ia_controls = extractedDescription.IAControls || undefined
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
control.tags = _.omitBy(control.tags, (value) => value === undefined)
|
|
120
|
+
|
|
121
|
+
// Get all identifiers from the rule
|
|
122
|
+
if (rule.ident) {
|
|
123
|
+
rule.ident.forEach((identifier) => {
|
|
124
|
+
// Get CCIs
|
|
125
|
+
if (identifier['@_system'].toLowerCase().includes('cci')) {
|
|
126
|
+
if (!('cci' in control.tags)) {
|
|
127
|
+
control.tags.cci = []
|
|
128
|
+
}
|
|
129
|
+
control.tags.cci?.push(identifier['#text'])
|
|
130
|
+
}
|
|
131
|
+
// Get legacy identifiers
|
|
132
|
+
else if (identifier['@_system'].toLowerCase().includes('legacy')) {
|
|
133
|
+
if (!('legacy' in control.tags)) {
|
|
134
|
+
control.tags.legacy = []
|
|
135
|
+
}
|
|
136
|
+
control.tags.legacy?.push(identifier['#text'])
|
|
137
|
+
}
|
|
138
|
+
// Get NIST identifiers
|
|
139
|
+
else if (identifier['@_system'].toLowerCase().includes('nist')) {
|
|
140
|
+
if (!('nist' in control.tags)) {
|
|
141
|
+
control.tags.nist = []
|
|
142
|
+
}
|
|
143
|
+
control.tags.nist?.push(identifier['#text'])
|
|
144
|
+
} else {
|
|
145
|
+
// console.log('Alert')
|
|
146
|
+
// console.log(identifier['@_system'])
|
|
147
|
+
// console.log(identifier['#text'])
|
|
148
|
+
}
|
|
149
|
+
})
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
rule.reference?.forEach((reference) => {
|
|
153
|
+
if (_.get(reference, '@_href') === '') {
|
|
154
|
+
control.refs?.push(_.get(reference, '#text'))
|
|
155
|
+
} else {
|
|
156
|
+
try {
|
|
157
|
+
const referenceText = _.get(reference, '#text') || ''
|
|
158
|
+
const referenceURL = _.get(reference, '@_href') || ''
|
|
159
|
+
if (referenceURL) {
|
|
160
|
+
const parsedURL = new URL(_.get(reference, '@_href'))
|
|
161
|
+
if (parsedURL.protocol.toLowerCase().includes('http') || parsedURL.protocol.toLowerCase().includes('https')) {
|
|
162
|
+
control.refs?.push({
|
|
163
|
+
ref: referenceText,
|
|
164
|
+
url: referenceURL
|
|
165
|
+
})
|
|
166
|
+
} else {
|
|
167
|
+
control.refs?.push({
|
|
168
|
+
ref: referenceText,
|
|
169
|
+
uri: referenceURL
|
|
170
|
+
})
|
|
171
|
+
}
|
|
172
|
+
} else {
|
|
173
|
+
if ('title' in reference) {
|
|
174
|
+
control.refs?.push(_.get(reference, 'title') as string)
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Add the reference to the control tags when seperated by §
|
|
179
|
+
if (typeof referenceText === 'string' && referenceText.indexOf('§') !== -1) {
|
|
180
|
+
const referenceParts = referenceText.split('§')
|
|
181
|
+
if (referenceParts.length == 2) {
|
|
182
|
+
let [identifierType, identifier] = referenceText.split('§')
|
|
183
|
+
identifierType = identifierType.toLowerCase();
|
|
184
|
+
if (!(identifierType in control.tags)) {
|
|
185
|
+
control.tags[identifierType] = [identifier]
|
|
186
|
+
} else if (Array.isArray(control.tags[identifierType])) {
|
|
187
|
+
control.tags[identifierType] = _.union(control.tags[identifierType] as ArrayLike<string>, [identifier])
|
|
188
|
+
} else {
|
|
189
|
+
console.warn(`Attempted to push identifier to control tags when identifier already exists: ${identifierType}: ${identifier}`)
|
|
190
|
+
}
|
|
191
|
+
} else {
|
|
192
|
+
console.warn("Reference parts of invalid length:")
|
|
193
|
+
console.log(referenceParts)
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
} catch (e){
|
|
197
|
+
console.warn(`Error parsing ref for control ${control.id}: `)
|
|
198
|
+
console.warn(JSON.stringify(reference, null, 2))
|
|
199
|
+
console.warn(e);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
49
202
|
})
|
|
50
203
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
control.tags.nist?.push(_.get(CCINistMappings, identifierText))
|
|
204
|
+
// Associate any CCIs with NIST tags
|
|
205
|
+
if (control.tags.cci) {
|
|
206
|
+
control.tags.cci.forEach((cci: string) => {
|
|
207
|
+
if (!('nist' in control.tags)) {
|
|
208
|
+
control.tags.nist = []
|
|
209
|
+
}
|
|
210
|
+
if (cci in CciNistMappingData.data) {
|
|
211
|
+
control.tags.nist?.push(_.get(CciNistMappingData.data, cci))
|
|
60
212
|
}
|
|
61
|
-
}
|
|
62
|
-
if (identifier['@_system'].toLowerCase().endsWith('legacy')) {
|
|
63
|
-
control.tags.legacy?.push(identifierText)
|
|
64
|
-
}
|
|
65
213
|
})
|
|
66
|
-
|
|
214
|
+
}
|
|
67
215
|
|
|
68
216
|
profile.controls.push(control)
|
|
69
217
|
})
|