@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.
@@ -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, DisaStig } from '../types/xccdf';
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 function processXCCDF(xml: string): Profile {
9
- const parsedXML: DisaStig = convertEncodedXmlIntoJson(xml)
10
- const groups = parsedXML.Benchmark.Group;
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
- groups.forEach(group => {
19
- const extractedDescription: DecodedDescription = convertEncodedHTMLIntoJson(group.Rule?.description)
20
- const control = new Control({
21
- id: group['@_id'],
22
- title: group.Rule['@_severity'] ? group.Rule.title : `[[[MISSING SEVERITY FROM STIG]]] ${group.Rule.title}`,
23
- desc: extractedDescription.VulnDiscussion?.split('Satisfies: ')[0],
24
- impact: severityStringToImpact(group.Rule['@_severity'] || 'critical'),
25
- descs: {
26
- check: group.Rule.check['check-content'],
27
- fix: group.Rule.fixtext['#text']
28
- },
29
- tags: _.omitBy({
30
- severity: impactNumberToSeverityString(severityStringToImpact(group.Rule['@_severity'] || 'critical')),
31
- gtitle: group.title,
32
- satisfies: extractedDescription.VulnDiscussion?.includes('Satisfies: ') && extractedDescription.VulnDiscussion.split('Satisfies: ').length >= 1 ? extractedDescription.VulnDiscussion.split('Satisfies: ')[1].split(',').map(satisfaction => satisfaction.trim()) : undefined,
33
- gid: group['@_id'],
34
- rid: group.Rule['@_id'],
35
- stig_id: group.Rule.version,
36
- fix_id: group.Rule.fix['@_id'],
37
- false_negatives: extractedDescription.FalseNegatives,
38
- false_positives: extractedDescription.FalsePositives,
39
- documentable: extractedDescription.Documentable,
40
- mitigations: extractedDescription.Mitigations,
41
- severity_override_guidance: extractedDescription.SeverityOverrideGuidance,
42
- potential_impacts: extractedDescription.PotentialImpacts,
43
- third_party_tools: extractedDescription.ThirdPartyTools,
44
- mitigation_control: extractedDescription.MitigationControl, // This exists as mitigation_controls in inspec_tools, but is called mitigation_control in the xccdf, this shouldn't ever be defined but is still here for backwards compatibility
45
- mitigation_controls: extractedDescription.MitigationControls,
46
- responsibility: extractedDescription.Responsibility,
47
- ia_controls: extractedDescription.IAControls
48
- }, i => !Boolean(i))
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
- if ('ident' in group.Rule) {
52
- const identifiers = Array.isArray(group.Rule.ident) ? group.Rule.ident : [group.Rule.ident]
53
- // Grab CCI/NIST/Legacy identifiers
54
- identifiers.forEach(identifier => {
55
- const identifierText = identifier['#text']
56
- if (identifier['@_system'].toLowerCase().endsWith('cci')) {
57
- control.tags.cci?.push(identifierText)
58
- if (identifierText in CCINistMappings) {
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
  })