Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Checklist metadata validation #2637

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 6 additions & 2 deletions src/commands/convert/ckl2hdf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,11 @@ export default class CKL2HDF extends Command {
const data = fs.readFileSync(flags.input, 'utf8')
checkInput({data, filename: flags.input}, 'checklist', 'DISA Checklist')

const converter = new Mapper(data, flags['with-raw'])
fs.writeFileSync(checkSuffix(flags.output), JSON.stringify(converter.toHdf()))
try {
const converter = new Mapper(data, flags['with-raw'])
fs.writeFileSync(checkSuffix(flags.output), JSON.stringify(converter.toHdf()))
} catch (error) {
console.error(`Error converting to hdf:\n${error}`)
}
}
}
72 changes: 23 additions & 49 deletions src/commands/convert/hdf2ckl.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,7 @@
import {Command, Flags} from '@oclif/core'
import {contextualizeEvaluation} from 'inspecjs'
import _ from 'lodash'
import fs from 'fs'
import {v4} from 'uuid'
import {default as files} from '../../resources/files.json'
import Mustache from 'mustache'
import {CKLMetadata} from '../../types/checklist'
import {convertFullPathToFilename, getProfileInfo} from '../../utils/global'
import {getDetails} from '../../utils/checklist'
import {Assettype, ChecklistMetadata, ChecklistResults as Mapper, Role, Techarea, validateChecklistMetadata} from '@mitre/hdf-converters'

Check failure on line 4 in src/commands/convert/hdf2ckl.ts

View workflow job for this annotation

GitHub Actions / build

'"@mitre/hdf-converters"' has no exported member named 'validateChecklistMetadata'. Did you mean 'ChecklistMetadata'?

export default class HDF2CKL extends Command {
static usage = 'convert hdf2ckl -i <hdf-scan-results-json> -o <output-ckl> [-h] [-m <metadata>] [-H <hostname>] [-F <fqdn>] [-M <mac-address>] [-I <ip-address>]'
Expand All @@ -25,54 +19,34 @@
ip: Flags.string({char: 'I', required: false, description: 'IP address for CKL metadata'}),
}

static examples = ['saf convert hdf2ckl -i rhel7-results.json -o rhel7.ckl --fqdn reverseproxy.example.org --hostname reverseproxy --ip 10.0.0.3 --mac 12:34:56:78:90']
static examples = ['saf convert hdf2ckl -i rhel7-results.json -o rhel7.ckl --fqdn reverseproxy.example.org --hostname reverseproxy --ip 10.0.0.3 --mac 12:34:56:78:90:AB']

async run() {
const {flags} = await this.parse(HDF2CKL)
const contextualizedEvaluation = contextualizeEvaluation(JSON.parse(fs.readFileSync(flags.input, 'utf8')))
const profileName = contextualizedEvaluation.data.profiles[0].name
const controls = contextualizedEvaluation.contains.flatMap(profile => profile.contains) || []
const rootControls = _.uniqBy(controls, control =>
_.get(control, 'root.hdf.wraps.id'),
).map(({root}) => root)
let cklData = {}
const cklMetadata: CKLMetadata = {
fileName: convertFullPathToFilename(flags.input),
benchmark: {
title: profileName || null,
version: '1',
plaintext: null,
},
stigid: profileName || null,
role: 'None',
type: 'Computing',
hostname: flags.hostname || _.get(contextualizedEvaluation, 'evaluation.data.passthrough.hostname') || null,
ip: flags.ip || _.get(contextualizedEvaluation, 'evaluation.data.passthrough.ip') || null,
mac: flags.mac || _.get(contextualizedEvaluation, 'evaluation.data.passthrough.mac') || null,
fqdn: flags.fqdn || _.get(contextualizedEvaluation, 'evaluation.data.passthrough.fqdn') || null,
tech_area: null,
target_key: '0',
web_or_database: 'false',
web_db_site: null,
web_db_instance: null,
}

if (flags.metadata) {
const cklMetadataInput: CKLMetadata = JSON.parse(fs.readFileSync(flags.metadata, 'utf8'))
for (const field in cklMetadataInput) {
if (typeof cklMetadata[field] === 'string' || typeof cklMetadata[field] === 'object') {
cklMetadata[field] = cklMetadataInput[field]
}
}
/* Order of precedence for checklist metadata:
command flags (hostname, ip, etc.)
metadata flag
input hdf file passthrough.metadata
input hdf file passthrough.checklist.asset */

const defaultMetadata: ChecklistMetadata = {
role: Role.None, assettype: Assettype.Computing, webordatabase: 'false', profiles: [],
hostfqdn: '', hostip: '', hostmac: '', marking: '', techarea: Techarea.Empty, vulidmapping: 'id',
hostname: '', targetcomment: '', webdbinstance: '', webdbsite: '',
}
const inputHDF = JSON.parse(fs.readFileSync(flags.input, 'utf8'))
const flagMetadata = {hostname: flags.hostname, hostip: flags.ip, hostmac: flags.mac, hostfqdn: flags.fqdn}
const fileMetadata = flags.metadata ? JSON.parse(fs.readFileSync(flags.metadata, 'utf8')) : {}
const hdfMetadata = _.get(inputHDF, 'passthrough.metadata', _.get(inputHDF, 'passthrough.checklist.asset', {}))
const metadata = _.merge(defaultMetadata, hdfMetadata, fileMetadata, flagMetadata)
_.set(inputHDF, 'passthrough.metadata', metadata)

cklData = {
releaseInfo: cklMetadata.benchmark.plaintext,
...cklMetadata,
profileInfo: getProfileInfo(contextualizedEvaluation, cklMetadata.fileName),
uuid: v4(),
controls: rootControls.map(control => getDetails(control, profileName)),
const validationResults = validateChecklistMetadata(metadata)
if (validationResults.ok) {
fs.writeFileSync(flags.output, new Mapper(inputHDF).toCkl())
} else {
console.error(`Error creating checklist:\n${validationResults.error.message}`)
}
fs.writeFileSync(flags.output, Mustache.render(files['cklExport.ckl'].data, cklData).replaceAll(/[^\x00-\x7F]/g, ''))
}
}
71 changes: 55 additions & 16 deletions src/commands/generate/ckl_metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,33 @@
import fs from 'fs'
import promptSync from 'prompt-sync'
import _ from 'lodash'
import {ChecklistMetadata, validateChecklistMetadata} from '@mitre/hdf-converters'

Check failure on line 5 in src/commands/generate/ckl_metadata.ts

View workflow job for this annotation

GitHub Actions / build

'"@mitre/hdf-converters"' has no exported member named 'validateChecklistMetadata'. Did you mean 'ChecklistMetadata'?

const prompt = promptSync()

// Ensures that no empty strings are passed into the metadata
function noEmpty(ask: string) : string | undefined {
const response = prompt({ask})
if (response)
return response
return undefined
}

function enforceInteger(ask: string) {
let response = prompt({ask})
let intRep: number
while (true) {
intRep = Number.parseInt(response, 10)
const floatRep = Number.parseFloat(response)
if (intRep === floatRep && intRep >= 0 && !Number.isNaN(intRep))
break
console.log(`${response} is not a valid non-negative integer. Please try again`)
response = prompt({ask})
}

return intRep
}

export default class GenerateCKLMetadata extends Command {
static usage = 'generate ckl_metadata -o <json-file> [-h]'

Expand All @@ -21,23 +45,38 @@
const {flags} = await this.parse(GenerateCKLMetadata)
console.log('Please fill in the following fields to the best of your ability, if you do not have a value, please leave the field empty.')
const cklMetadata = {
benchmark: {
title: prompt({ask: 'What is the benchmark title? '}) || null,
version: prompt({ask: 'What is the benchmark version? '}) || null,
plaintext: prompt({ask: 'What is the notes for release info? '}) || null,
},
stigid: prompt({ask: 'What is the STIG ID? '}) || null,
role: prompt({ask: 'What is the computing role? (None/Workstation/Member Server/Domain Controller) '}) || null,
type: _.capitalize(prompt({ask: 'What is the asset type? (Computing/Non-Computing) '})) || null,
hostname: prompt({ask: 'What is the asset hostname? '}) || null,
ip: prompt({ask: 'What is the asset IP address? '}) || null,
mac: prompt({ask: 'What is the asset MAC address? '}) || null,
tech_area: prompt({ask: 'What is the tech area? (Application Review/Boundary Security/CDS Admin Review/CDS Technical Review/Database Review/Domain Name System (DNS)/Exchange Server/Host Based System Security (HBSS)/Internal Network/Mobility/Releasable Networks (REL)/Releaseable Networks (REL)/Traditional Security/UNIX OS/VVOIP Review/Web Review/Windows OS/Other Review) '}) || null, // Yes, these typos really are how the enumerations are defined in STIG viewer's source code
target_key: prompt({ask: 'What is the target key? '}) || null,
web_or_database: prompt({ask: 'Is the target a web or database? (y/n) '}).toLowerCase() === 'y',
web_db_site: prompt({ask: 'What is the Web or DB site? '}) || null,
web_db_instance: prompt({ask: 'What is the Web or DB instance? '}) || null,
profiles: [
{
name: noEmpty('What is the benchmark name? (Must match with profile name listed in HDF) '),
title: noEmpty('What is the benchmark title? '),
version: enforceInteger('What is the benchmark version? '),
releasenumber: enforceInteger('What is the benchmark release number? '),
releasedate: noEmpty('What is the benchmark release date (YYYY/MM/DD)? '),
showCalendar: true,
},
],
marking: noEmpty('What is the marking? '),
hostname: noEmpty('What is the asset hostname? '),
hostip: noEmpty('What is the asset IP address? '),
hostmac: noEmpty('What is the asset MAC address? '),
hostfqdn: noEmpty('What is the asset FQDN? '),
targetcomment: noEmpty('What are the target comments? '),
role: noEmpty('What is the computing role? (None/Workstation/Member Server/Domain Controller) '),
assettype: noEmpty('What is the asset type? (Computing/Non-Computing) '),
techarea: noEmpty('What is the tech area? (Application Review/Boundary Security/CDS Admin Review/CDS Technical Review/Database Review/Domain Name System (DNS)/Exchange Server/Host Based System Security (HBSS)/Internal Network/Mobility/Releasable Networks (REL)/Releaseable Networks (REL)/Traditional Security/UNIX OS/VVOIP Review/Web Review/Windows OS/Other Review) '), // Yes, these typos really are how the enumerations are defined in STIG viewer's source code
stigguid: noEmpty('What is the STIG ID? '),
targetkey: noEmpty('What is the target key? '),
webordatabase: String(prompt({ask: 'Is the target a web or database? (y/n) '}).toLowerCase() === 'y'),
webdbsite: noEmpty('What is the Web or DB site? '),
webdbinstance: noEmpty('What is the Web or DB instance? '),
vulidmapping: noEmpty('Use gid or id for vuln number? (gid/id) '),
}
const validationResults = validateChecklistMetadata(cklMetadata as ChecklistMetadata)
if (!validationResults.ok) {
console.error(`Unable to generate checklist metadata:\n${validationResults.error.message}`)
process.exit(1)
}

fs.writeFileSync(flags.output, JSON.stringify(cklMetadata))
}
}
Loading
Loading