@mitre/inspec-objects 0.0.3 → 0.0.4

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,148 +0,0 @@
1
- import { ExecJSON } from "inspecjs";
2
- import _ from "lodash";
3
- import {flatten, unflatten} from "flat"
4
- import { escapeQuotes, unformatText, wrapAndEscapeQuotes } from "../utilities/global";
5
-
6
- export default class Control {
7
- id?: string | null;
8
- title?: string | null;
9
- code?: string | null;
10
- desc?: string | null;
11
- descs?: ExecJSON.ControlDescription[] | { [key: string]: string | undefined } | null;
12
- impact?: number;
13
- ref?: string;
14
- refs?: (string | {
15
- ref?: string;
16
- url?: string;
17
- uri?: string;
18
- })[];
19
- tags: {
20
- check?: string;
21
- fix?: string;
22
- severity?: string;
23
- gtitle?: string;
24
- gid?: string;
25
- satisfies?: string[];
26
- rid?: string;
27
- stig_id?: string;
28
- fix_id?: string | null;
29
- cci?: string[];
30
- cis_controls?: Record<string, string[]>[];
31
- nist?: string[];
32
- legacy?: string[];
33
- false_negatives?: string;
34
- false_positives?: string;
35
- documentable?: boolean;
36
- mitigations?: string;
37
- severity_override_guidance?: string;
38
- potential_impacts?: string;
39
- third_party_tools?: string;
40
- mitigation_controls?: string;
41
- responsibility?: string;
42
- ia_controls?: string;
43
- [key: string]:
44
- | string
45
- | string[]
46
- | Record<string, string[]>[]
47
- | boolean
48
- | undefined
49
- | null;
50
- } = {};
51
-
52
- constructor(data?: Partial<Control>) {
53
- this.refs = []
54
- this.tags = {}
55
- if (data) {
56
- Object.entries(data).forEach(([key, value]) => {
57
- _.set(this, key, value);
58
- });
59
- }
60
- }
61
-
62
- toUnformattedObject(): Control {
63
- const flattened: Record<string, string | number> = flatten(this)
64
-
65
- Object.entries(flattened).forEach(([key, value]) => {
66
- if(typeof value === 'string') {
67
- _.set(flattened, key, unformatText(value));
68
- }
69
- });
70
-
71
- return new Control(unflatten(flattened));
72
- }
73
-
74
- toRuby(lineLength: number = 80) {
75
- let result = "# encoding: UTF-8\n\n";
76
-
77
- result += `control "${this.id}" do\n`;
78
- if (this.title) {
79
- result += ` title "${wrapAndEscapeQuotes(this.title, lineLength)}"\n`;
80
- } else {
81
- console.error(`${this.id} does not have a title`);
82
- }
83
-
84
- if (this.desc) {
85
- result += ` desc "${wrapAndEscapeQuotes(this.desc, lineLength)}"\n`;
86
- } else {
87
- console.error(`${this.id} does not have a desc`);
88
- }
89
-
90
- if (this.descs) {
91
- Object.entries(this.descs).forEach(([key, desc]) => {
92
- if (desc) {
93
- result += ` desc "${key}", "${wrapAndEscapeQuotes(
94
- desc,
95
- lineLength
96
- )}"\n`;
97
- } else {
98
- console.error(`${this.id} does not have a desc for the value ${key}`);
99
- }
100
- });
101
- }
102
-
103
- if (this.impact) {
104
- result += ` impact ${this.impact}\n`;
105
- } else {
106
- console.error(`${this.id} does not have an impact`);
107
- }
108
-
109
- if (this.refs) {
110
- this.refs.forEach((ref) => {
111
- if (typeof ref === 'string') {
112
- result += ` ref '${escapeQuotes(ref)}'\n`;
113
- } else {
114
- result += ` ref '${escapeQuotes(ref.ref || '')}', url: '${escapeQuotes(ref.url || '')}'`
115
- }
116
-
117
- });
118
- }
119
-
120
- Object.entries(this.tags).forEach(([tag, value]) => {
121
- if (value) {
122
- if (typeof value === "object") {
123
- if (Array.isArray(value) && typeof value[0] === "string") {
124
- result += ` tag ${tag}: ${JSON.stringify(value)}\n`;
125
- } else {
126
- // Convert JSON Object to Ruby Hash
127
- const stringifiedObject = JSON.stringify(value, null, 2)
128
- .replace(/\n/g, "\n ")
129
- .replace(/\{\n {6}/g, "{")
130
- .replace(/\[\n {8}/g, "[")
131
- .replace(/\n {6}\]/g, "]")
132
- .replace(/\n {4}\}/g, "}")
133
- .replace(/": \[/g, '" => [');
134
- result += ` tag ${tag}: ${stringifiedObject}\n`;
135
- }
136
- } else if (typeof value === "string") {
137
- result += ` tag ${tag}: "${wrapAndEscapeQuotes(
138
- value,
139
- lineLength
140
- )}"\n`;
141
- }
142
- }
143
- });
144
- result += "end";
145
-
146
- return result;
147
- }
148
- }
@@ -1,93 +0,0 @@
1
- import Control from "./control";
2
- import YAML from "yaml";
3
- import _ from "lodash";
4
- import { unformatText } from "../utilities/global";
5
-
6
- export default class Profile {
7
- name?: string | null;
8
- title?: string | null;
9
- maintainer?: string | null;
10
- copyright?: string | null;
11
- copyright_email?: string | null;
12
- license?: string | null;
13
- summary?: string | null;
14
- description?: string | null;
15
- version?: string | null;
16
- inspec_version?: string | null;
17
- supports: {
18
- "platform-family"?: string;
19
- "platform-name"?: string;
20
- "os-name"?: string;
21
- "os-family"?: string;
22
- release?: string;
23
- platform?: string;
24
- }[] = [];
25
- depends: {
26
- // Required for all
27
- name: string; // Required for all
28
-
29
- // Local file
30
- path?: string; // Local path on disk
31
-
32
- // Remote HTTP(s)
33
- url?: string; // Remote URL tarball
34
- username?: string; // HTTP Basic Authentication Username
35
- password?: string; // HTTP Basic Authentication Password
36
-
37
- // Git Repository
38
- git?: string;
39
- branch?: string;
40
- tag?: string;
41
- commit?: string;
42
- version?: string;
43
- relative_path?: string;
44
-
45
- // Chef Supermarket
46
- supermarket?: string;
47
-
48
- // Base Compliance
49
- compliance?: string;
50
- }[] = [];
51
- inputs: { [key: string]: string }[] = [];
52
- gem_dependencies?: {name: string, version: string}[];
53
- libraries: string[] = [];
54
- readme?: string | null;
55
- files: string[] = [];
56
- controls: Control[] = [];
57
-
58
- constructor(data?: Omit<Partial<Profile>, "controls">) {
59
- if (data) {
60
- Object.entries(data).forEach(([key, value]) => {
61
- _.set(this, key, value);
62
- });
63
- }
64
- }
65
-
66
- createInspecYaml(): string {
67
- return YAML.stringify({
68
- name: this.name,
69
- title: this.title,
70
- maintainer: this.maintainer,
71
- copyright: this.copyright,
72
- copyright_email: this.copyright_email,
73
- license: this.license,
74
- summary: this.summary,
75
- description: this.description,
76
- version: this.version,
77
- supports: this.supports,
78
- depends: this.depends,
79
- inspec_version: this.inspec_version,
80
- });
81
- }
82
-
83
- toUnformattedObject(): Profile {
84
- const unformattedProfile: Profile = new Profile(this);
85
- Object.entries(this).forEach(([key, value]) => {
86
- if (typeof value === "string") {
87
- _.set(unformattedProfile, key, unformatText(value));
88
- }
89
- });
90
- unformattedProfile.controls = this.controls.map((control) => control.toUnformattedObject())
91
- return unformattedProfile;
92
- }
93
- }
@@ -1,92 +0,0 @@
1
- import {
2
- ContextualizedEvaluation,
3
- ContextualizedProfile,
4
- contextualizeEvaluation,
5
- contextualizeProfile,
6
- ConversionResult,
7
- convertFile,
8
- ExecJSON
9
- } from "inspecjs";
10
- import _ from "lodash";
11
- import Control from "../objects/control";
12
- import Profile from "../objects/profile";
13
-
14
- export function processEvaluation(evaluationInput: ContextualizedEvaluation) {
15
- const topLevelProfile = evaluationInput.contains[0];
16
- const profile = new Profile({
17
- name: topLevelProfile.data.name,
18
- title: topLevelProfile.data.title,
19
- maintainer: topLevelProfile.data.maintainer,
20
- copyright: topLevelProfile.data.copyright,
21
- copyright_email: topLevelProfile.data.copyright_email,
22
- license: _.get(topLevelProfile.data, "license"),
23
- summary: _.get(topLevelProfile.data, "summary"),
24
- description: _.get(topLevelProfile.data, "description"),
25
- version: topLevelProfile.data.version,
26
- });
27
- topLevelProfile.contains.forEach((control) => {
28
- profile.controls.push(
29
- new Control({
30
- id: control.data.id,
31
- title: control.data.title,
32
- impact: control.data.impact,
33
- desc: control.data.desc,
34
- descs: control.hdf.wraps.descriptions,
35
- tags: control.hdf.wraps.tags,
36
- })
37
- );
38
- });
39
- return profile;
40
- }
41
-
42
- export function processProfileJSON(
43
- profileInput: ContextualizedProfile
44
- ): Profile {
45
- const profile = new Profile({
46
- name: profileInput.data.name,
47
- title: profileInput.data.title,
48
- maintainer: profileInput.data.maintainer,
49
- copyright: profileInput.data.copyright,
50
- copyright_email: profileInput.data.copyright_email,
51
- license: _.get(profileInput.data, "license"),
52
- summary: _.get(profileInput.data, "summary"),
53
- description: _.get(profileInput.data, "description"),
54
- version: profileInput.data.version,
55
- });
56
- profileInput.data.controls.forEach((control) => {
57
- profile.controls.push(
58
- new Control({
59
- id: control.id,
60
- title: control.title,
61
- desc: control.desc,
62
- impact: control.impact,
63
- code: control.code,
64
- tags: control.tags,
65
- descs: control.descriptions,
66
- })
67
- );
68
- });
69
- return profile;
70
- }
71
-
72
- export function processExecJSON(execJSON: ExecJSON.Execution) {
73
- return processEvaluation(contextualizeEvaluation(execJSON));
74
- }
75
-
76
- export function processJSON(json: string): Profile {
77
- const convertedFile: ConversionResult = convertFile(json, true);
78
- let profile = new Profile();
79
- if (convertedFile["1_0_ExecJson"]) {
80
- profile = processEvaluation(
81
- contextualizeEvaluation(convertedFile["1_0_ExecJson"])
82
- ).toUnformattedObject();
83
- } else if (convertedFile["1_0_ProfileJson"]) {
84
- profile = processProfileJSON(contextualizeProfile(JSON.parse(json))).toUnformattedObject();
85
- } else {
86
- throw new Error("Unknown file type passed");
87
- }
88
-
89
- profile.controls = _.sortBy(profile.controls, "id");
90
-
91
- return profile;
92
- }
@@ -1,18 +0,0 @@
1
- import { convertEncodedXmlIntoJson } from "../utilities/xccdf"
2
- import {OvalDefinitionValue, Oval} from '../types/oval'
3
-
4
- export function processOVAL(oval: string): Record<string, OvalDefinitionValue> {
5
- const parsed: Oval = convertEncodedXmlIntoJson(oval)
6
-
7
- const extractedDefinitions: Record<string, OvalDefinitionValue> = {}
8
-
9
- for (const ovalDefinitions of parsed.oval_definitions) {
10
- for (const definitionList of ovalDefinitions.definitions) {
11
- for (const definition of definitionList.definition) {
12
- extractedDefinitions[definition["@_id"]] = definition
13
- }
14
- }
15
- }
16
-
17
- return extractedDefinitions
18
- }
@@ -1,252 +0,0 @@
1
- import Profile from '../objects/profile';
2
- import { convertEncodedHTMLIntoJson, convertEncodedXmlIntoJson, impactNumberToSeverityString, removeXMLSpecialCharacters, severityStringToImpact } from '../utilities/xccdf';
3
- import { BenchmarkGroup, BenchmarkRule, DecodedDescription, FrontMatter, Notice, ParsedXCCDF, RationaleElement } from '../types/xccdf';
4
- import Control from '../objects/control';
5
- import _ from 'lodash';
6
- import { OvalDefinitionValue } from '../types/oval';
7
- import {data as CciNistMappingData} from '../mappings/CciNistMappingData'
8
-
9
- export type GroupContextualizedRule = BenchmarkRule & {group: Omit<BenchmarkGroup, 'Rule' | 'Group'>}
10
-
11
- export function extractAllRules(groups: BenchmarkGroup[]): GroupContextualizedRule[] {
12
- const rules: GroupContextualizedRule[] = [];
13
- groups.forEach((group) => {
14
- if (group.Rule) {
15
- rules.push(...(group.Rule.map((rule) => {
16
- return {
17
- ...rule,
18
- group: _.omit(group, ['Rule', 'Group'])
19
- }
20
- })))
21
- }
22
- if (group.Group) {
23
- rules.push(...extractAllRules(group.Group))
24
- }
25
- })
26
- return rules
27
- }
28
-
29
- export function processXCCDF(xml: string, removeNewlines = false, ovalDefinitions?: Record<string, OvalDefinitionValue>): Profile {
30
- const parsedXML: ParsedXCCDF = convertEncodedXmlIntoJson(xml)
31
- const rules = extractAllRules(parsedXML.Benchmark[0].Group)
32
-
33
- const profile = new Profile({
34
- name: parsedXML.Benchmark[0]['@_id'],
35
- title: (parsedXML.Benchmark[0].title[0] as FrontMatter)['#text'],
36
- summary: (parsedXML.Benchmark[0].description[0] as RationaleElement)['#text']
37
- });
38
-
39
- rules.forEach(rule => {
40
- let extractedDescription: string | DecodedDescription;
41
- if (Array.isArray(rule.description)) {
42
- extractedDescription = rule.description[0]['#text']
43
- } else {
44
- extractedDescription = convertEncodedHTMLIntoJson(rule.description)
45
- }
46
- const control = new Control();
47
-
48
- control.id = rule.group['@_id']
49
-
50
- if (removeNewlines) {
51
- const title = removeXMLSpecialCharacters(rule['@_severity'] ? rule.title : `[[[MISSING SEVERITY FROM STIG]]] ${rule.title}`)
52
- control.title = title.replace(/\n/g, '{{{{newlineHERE}}}}')
53
- const desc = removeXMLSpecialCharacters(typeof extractedDescription === 'string' ? extractedDescription : extractedDescription.VulnDiscussion?.split('Satisfies: ')[0] || 'Missing Description')
54
- control.desc = desc?.replace(/\n/g, '{{{{newlineHERE}}}}')
55
- } else {
56
- control.title = removeXMLSpecialCharacters(rule['@_severity'] ? rule.title : `[[[MISSING SEVERITY FROM STIG]]] ${rule.title}`)
57
- control.desc = removeXMLSpecialCharacters(typeof extractedDescription === 'string' ? extractedDescription : extractedDescription.VulnDiscussion?.split('Satisfies: ')[0] || 'Missing Description')
58
- }
59
- control.impact = severityStringToImpact(rule['@_severity'] || 'critical', rule.group['@_id'])
60
-
61
- if (!control.descs || Array.isArray(control.descs)) {
62
- control.descs = {}
63
- }
64
-
65
- if (rule.check) {
66
- if (rule.check.some((ruleValue) => 'check-content' in ruleValue)) {
67
- if (removeNewlines) {
68
- const check = removeXMLSpecialCharacters(rule.check ? rule.check[0]['check-content'] : 'Missing description')
69
- control.descs.check = check.replace(/\n/g, '{{{{newlineHERE}}}}')
70
- } else {
71
- control.descs.check = removeXMLSpecialCharacters(rule.check ? rule.check[0]['check-content'] : 'Missing description')
72
- }
73
-
74
- } else if (rule.check.some((ruleValue) => 'check-content-ref' in ruleValue) && ovalDefinitions) {
75
- let referenceID: string | null = null;
76
- for (const checkContent of rule.check) {
77
- if ('check-content-ref' in checkContent && checkContent['@_system'].includes('oval')) {
78
- for (const checkContentRef of checkContent['check-content-ref']) {
79
- if (checkContentRef['@_name']) {
80
- referenceID = checkContentRef['@_name']
81
- }
82
- }
83
- }
84
- }
85
- if (referenceID && referenceID in ovalDefinitions) {
86
- if (removeNewlines) {
87
- const check = removeXMLSpecialCharacters(ovalDefinitions[referenceID].metadata[0].title)
88
- control.descs.check = check.replace(/\n/g, '{{{{newlineHERE}}}}')
89
- } else {
90
- control.descs.check = removeXMLSpecialCharacters(ovalDefinitions[referenceID].metadata[0].title)
91
- }
92
- } else if (referenceID) {
93
- console.warn(`Could not find OVAL definition for ${referenceID}`)
94
- }
95
- }
96
- }
97
-
98
- if (removeNewlines) {
99
- const fix = removeXMLSpecialCharacters(rule.fixtext ? rule.fixtext[0]['#text'] : (rule.fix ? (rule.fix[0] as Notice)['#text'] || 'Missing fix text' : 'Missing fix text'))
100
- control.descs.fix = fix.replace(/\n/g, '{{{{newlineHERE}}}}')
101
- } else {
102
- control.descs.fix = removeXMLSpecialCharacters(rule.fixtext ? rule.fixtext[0]['#text'] : (rule.fix ? (rule.fix[0] as Notice)['#text'] || 'Missing fix text' : 'Missing fix text'))
103
- }
104
-
105
- control.tags.severity = impactNumberToSeverityString(severityStringToImpact(rule['@_severity'] || 'critical', control.id))
106
- control.tags.gid = rule.group['@_id'],
107
- control.tags.rid = rule['@_id']
108
- control.tags.stig_id = rule['version']
109
-
110
- if (typeof rule.group.title === "string") {
111
- control.tags.gtitle = removeXMLSpecialCharacters(rule.group.title)
112
- } else {
113
- control.tags.gtitle = removeXMLSpecialCharacters(_.get(rule.group, 'title[0].#text'))
114
- }
115
-
116
- if (rule['fix'] && rule['fix'].length > 0) {
117
- control.tags.fix_id = rule['fix'][0]['@_id']
118
- } else {
119
- control.tags.fix_id = null
120
- }
121
-
122
- if (rule['rationale']) {
123
- control.tags.rationale = rule['rationale'][0]['#text']
124
- } else {
125
- control.tags.rationale = null
126
- }
127
-
128
- if (typeof extractedDescription === 'object') {
129
- control.tags.satisfies = extractedDescription.VulnDiscussion?.includes('Satisfies: ') && extractedDescription.VulnDiscussion.split('Satisfies: ').length >= 1 ? extractedDescription.VulnDiscussion.split('Satisfies: ')[1].split(',').map(satisfaction => satisfaction.trim()) : undefined
130
- control.tags.false_negatives = extractedDescription.FalseNegatives || undefined
131
- control.tags.false_positives = extractedDescription.FalsePositives || undefined
132
- control.tags.documentable = typeof extractedDescription.Documentable === 'boolean' ? extractedDescription.Documentable : undefined
133
- control.tags.mitigations = extractedDescription.Mitigations || undefined
134
- control.tags.severity_override_guidance = extractedDescription.SeverityOverrideGuidance || undefined
135
- control.tags.potential_impacts = extractedDescription.PotentialImpacts || undefined
136
- control.tags.third_party_tools = extractedDescription.ThirdPartyTools || undefined
137
- control.tags.mitigation_control = extractedDescription.MitigationControl || undefined
138
- control.tags.mitigation_controls = extractedDescription.MitigationControls || undefined
139
- control.tags.responsibility = extractedDescription.Responsibility || undefined
140
- control.tags.ia_controls = extractedDescription.IAControls || undefined
141
- }
142
-
143
- control.tags = _.mapValues(_.omitBy(control.tags, (value) => value === undefined), (value) => {
144
- if (typeof value === 'string') {
145
- return removeXMLSpecialCharacters(value)
146
- } else {
147
- return value
148
- }
149
- })
150
-
151
- // Get all identifiers from the rule
152
- if (rule.ident) {
153
- rule.ident.forEach((identifier) => {
154
- // Get CCIs
155
- if (identifier['@_system'].toLowerCase().includes('cci')) {
156
- if (!('cci' in control.tags)) {
157
- control.tags.cci = []
158
- }
159
- control.tags.cci?.push(identifier['#text'])
160
- }
161
- // Get legacy identifiers
162
- else if (identifier['@_system'].toLowerCase().includes('legacy')) {
163
- if (!('legacy' in control.tags)) {
164
- control.tags.legacy = []
165
- }
166
- control.tags.legacy?.push(identifier['#text'])
167
- }
168
- // Get NIST identifiers
169
- else if (identifier['@_system'].toLowerCase().includes('nist')) {
170
- if (!('nist' in control.tags)) {
171
- control.tags.nist = []
172
- }
173
- control.tags.nist?.push(identifier['#text'])
174
- } else {
175
- // console.log('Alert')
176
- // console.log(identifier['@_system'])
177
- // console.log(identifier['#text'])
178
- }
179
- })
180
- }
181
-
182
- rule.reference?.forEach((reference) => {
183
- if (_.get(reference, '@_href') === '') {
184
- control.refs?.push(_.get(reference, '#text'))
185
- } else {
186
- try {
187
- const referenceText = _.get(reference, '#text') || ''
188
- const referenceURL = _.get(reference, '@_href') || ''
189
- if (referenceURL) {
190
- const parsedURL = new URL(_.get(reference, '@_href'))
191
- if (parsedURL.protocol.toLowerCase().includes('http') || parsedURL.protocol.toLowerCase().includes('https')) {
192
- control.refs?.push({
193
- ref: referenceText,
194
- url: referenceURL
195
- })
196
- } else {
197
- control.refs?.push({
198
- ref: referenceText,
199
- uri: referenceURL
200
- })
201
- }
202
- } else {
203
- if ('title' in reference) {
204
- control.refs?.push(_.get(reference, 'title') as string)
205
- }
206
- }
207
-
208
- // Add the reference to the control tags when seperated by §
209
- if (typeof referenceText === 'string' && referenceText.indexOf('§') !== -1) {
210
- const referenceParts = referenceText.split('§')
211
- if (referenceParts.length == 2) {
212
- let [identifierType, identifier] = referenceText.split('§')
213
- identifierType = identifierType.toLowerCase();
214
- if (!(identifierType in control.tags)) {
215
- control.tags[identifierType] = [identifier]
216
- } else if (Array.isArray(control.tags[identifierType])) {
217
- control.tags[identifierType] = _.union(control.tags[identifierType] as ArrayLike<string>, [identifier])
218
- } else {
219
- console.warn(`Attempted to push identifier to control tags when identifier already exists: ${identifierType}: ${identifier}`)
220
- }
221
- } else {
222
- console.warn("Reference parts of invalid length:")
223
- console.log(referenceParts)
224
- }
225
- }
226
- } catch (e){
227
- console.warn(`Error parsing ref for control ${control.id}: `)
228
- console.warn(JSON.stringify(reference, null, 2))
229
- console.warn(e);
230
- }
231
- }
232
- })
233
-
234
- // Associate any CCIs with NIST tags
235
- if (control.tags.cci) {
236
- control.tags.cci.forEach((cci: string) => {
237
- if (!('nist' in control.tags)) {
238
- control.tags.nist = []
239
- }
240
- if (cci in CciNistMappingData) {
241
- control.tags.nist?.push(_.get(CciNistMappingData, cci))
242
- }
243
- })
244
- }
245
-
246
- profile.controls.push(control)
247
- })
248
-
249
- profile.controls = _.sortBy(profile.controls, 'id')
250
-
251
- return profile.toUnformattedObject()
252
- }
@@ -1,9 +0,0 @@
1
- import Control from "../objects/control"
2
-
3
- export type ProfileDiff = {
4
- removedControlIDs: string[];
5
- addedControlIDs: string[];
6
- changedControls: {
7
- [key: string]: Partial<Control>;
8
- }
9
- }