Skip to content

Commit

Permalink
Delta process supporting fixes (#3098)
Browse files Browse the repository at this point in the history
* Fixed case where inspec json file was not provided and logger would error due to flag not set

Signed-off-by: George M Dias <GDIAS@MITRE.ORG>

* Fixed deepsource code findings

Signed-off-by: George M Dias <GDIAS@MITRE.ORG>

* Added documentation to the delta command

Signed-off-by: George M Dias <GDIAS@MITRE.ORG>

* Added documentation to the delta cli command

Signed-off-by: George M Dias <GDIAS@MITRE.ORG>

---------

Signed-off-by: George M Dias <GDIAS@MITRE.ORG>
Co-authored-by: Daniel Medina <dmedina@mitre.org>
  • Loading branch information
georgedias and DMedina6 authored Dec 20, 2024
1 parent c01a400 commit 72d31b3
Show file tree
Hide file tree
Showing 2 changed files with 38 additions and 13 deletions.
27 changes: 23 additions & 4 deletions src/commands/generate/delta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,16 @@ import inquirer from 'inquirer'
import inquirerFileTreeSelection from 'inquirer-file-tree-selection-prompt'
import {EventEmitter} from 'events'

/**
* This class extends the capabilities of the update_controls4delta providing the following capabilities:
* 1 - Creates new controls found in updated guidances
* 2 - Fuzzy matching capability (optional)
* a - Maps controls based on similarity and not control IDs
* b - For controls which a match is found, the describe block (code)
* within the old control is mapped over to the new control
* 3 - Detailed logging
* a - report file (.md), mapping statistics (CliProcessOutput.log)
*/
export default class GenerateDelta extends BaseCommand<typeof GenerateDelta> {
static description = 'Update an existing InSpec profile with updated XCCDF guidance'

Expand Down Expand Up @@ -285,8 +295,10 @@ export default class GenerateDelta extends BaseCommand<typeof GenerateDelta> {
logger.info(' Updating controls with new control number')
printCyan('Updating Controls ===========================================================================')

// For each control, modify the control file in the old controls directory
// then regenerate json profile
// We need to update controls that a mapping were found executing the mapControls method.
// This is needed because when we re-generate the new profile summary we need the controls
// to have the new name/Id. So, for each control, modify the control file in the old controls
// directory with the proper name and Id, than regenerate json profile summary.
// eslint-disable-next-line guard-for-in
for (const key in controls) {
const sourceShortControlFile = path.join(shortProfileDir, `${controls[key]}.rb`)
Expand Down Expand Up @@ -339,7 +351,7 @@ export default class GenerateDelta extends BaseCommand<typeof GenerateDelta> {
}
}

// Regenerate the profile json based on the updated mapped controls
// Regenerate the profile json summary based on the updated mapped controls
try {
logger.info(` Generating the profile json using the new mapped controls on: '${mappedDir}'`)
// Get the directory name without the trailing "controls" directory
Expand Down Expand Up @@ -445,7 +457,14 @@ export default class GenerateDelta extends BaseCommand<typeof GenerateDelta> {
// Call the .toRuby verbose if the log level is debug or verbose
const processLogLevel = Boolean(logLevel === 'debug' || logLevel === 'verbose')
if (index >= 0) {
// We found a mapping for this control
// We found a mapping for this control (aka index >=0)
// The new control (control) has the new metadata but doesn't have
// the describe block (code). Using the updateControl method with the new
// control so we can get the code with the new metadata.

// eslint-disable-next-line no-warning-comments
// TODO: Can use the getExistingDescribeFromControl(existingProfile.controls[index])
// method from inspect-objects
const newControl = updateControl(existingProfile.controls[index], control, logger)

logger.debug(`Writing updated control with code block for: ${control.id}.`)
Expand Down
24 changes: 15 additions & 9 deletions src/commands/generate/update_controls4delta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,14 +149,13 @@ export default class GenerateUpdateControls extends BaseCommand<typeof GenerateU
logger.info('Verifying that a controls folder exists...')
if (fs.existsSync(flags.controlsDir)) {
logger.debug(' Found controls directory')
fs.readdir(flags.controlsDir, function (err, files) { // skipcq: JS-0241
if (err) {
logger.error(`ERROR: Checking if controls directory is empty, received: ${err.message}`)
throw new Error(`Error checking controls directory, error: ${err.message}`)
} else if (files.length) {
try {
const files = await readdir(flags.controlsDir)
if (files.length) {
logger.debug(` Found ${files.length} Controls in the controls directory`)
if (flags.backupControls) {
// Create the backup directory inside the parent controls directory
// eslint-disable-next-line max-depth
if (fs.existsSync(GenerateUpdateControls.backupDir)) {
fs.rmSync(GenerateUpdateControls.backupDir, {recursive: true, force: true})
}
Expand All @@ -168,16 +167,24 @@ export default class GenerateUpdateControls extends BaseCommand<typeof GenerateU
logger.error(`No controls were found in the provide directory: ${flags.controlsDir}`)
throw new Error(`No controls were found in the provide directory: ${flags.controlsDir}`)
}
})
} catch (error: any) {
logger.error(`ERROR: Checking if controls directory is empty, received: ${error.message}`)
throw new Error(`Error checking controls directory, error: ${error.message}`)
}
} else {
throw new Error('Controls folder not specified or does not exist')
}

// Shorten the controls directory to sow the 'controls' directory and its parent
const shortControlsDir = path.sep + path.basename(path.dirname(flags.controlsDir)) +
path.sep + path.basename(flags.controlsDir)

//-------------------------------------------------------------------------
// Check if we have an InSpec json file, generate if not provided
// Process the InSpec json content, convert entries into a Profile object
logger.info(`Processing the Input execution/profile JSON file: ${path.basename(flags.inspecJsonFile!)}...`) // skipcq: JS-0339
logger.info('Processing the Input execution/profile JSON summary...')
if (flags.inspecJsonFile) {
logger.info(` Using execution/profile summary file: ${path.basename(flags.inspecJsonFile!)}`) // skipcq: JS-0339
try {
if (fs.lstatSync(flags.inspecJsonFile).isFile()) {
const inspecJsonFile = flags.inspecJsonFile
Expand All @@ -199,7 +206,7 @@ export default class GenerateUpdateControls extends BaseCommand<typeof GenerateU
} else {
// Generate the profile json
try {
logger.info(`Generating the profile json using inspec json command on '${flags.controlsDir}'`)
logger.info(` Generating the summary file on directory: ${shortControlsDir}`)
// Get the directory name without the trailing "controls" directory
const profileDir = path.dirname(flags.controlsDir)
const inspecJsonFile = execSync(`inspec json '${profileDir}'`, {encoding: 'utf8', maxBuffer: 50 * 1024 * 1024})
Expand Down Expand Up @@ -326,7 +333,6 @@ export default class GenerateUpdateControls extends BaseCommand<typeof GenerateU
// Updated controls have:
// Metadata from XCCDF guidances
// Code block from matching old control (inspec json)
const shortControlsDir = path.sep + path.basename(path.dirname(flags.controlsDir)) + path.sep + path.basename(flags.controlsDir)
logger.info(`Updating controls in directory: ..${shortControlsDir}`)

const ext = '.rb'
Expand Down

0 comments on commit 72d31b3

Please sign in to comment.