Skip to content

Commit

Permalink
feat: support for STIG Manager v1.1.0 (#12)
Browse files Browse the repository at this point in the history
  • Loading branch information
csmig authored Nov 18, 2021
1 parent 5a8c070 commit 351f6ef
Show file tree
Hide file tree
Showing 7 changed files with 610 additions and 451 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
</p>
<h1 align="center"> STIG Manager Watcher </h1>

<a href="https://npmjs.org/package/stigman-watcher"><img src="https://img.shields.io/badge/npm-1.1.2-green"></a>
<a href="https://npmjs.org/package/stigman-watcher"><img src="https://img.shields.io/badge/npm-1.2.0-green"></a>

A [STIG Manager](https://github.com/nuwcdivnpt/stig-manager) CLI client that watches a path for test result files formatted as CKL or XCCDF and posts the results to a Collection.

Expand Down
28 changes: 20 additions & 8 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#!/usr/bin/env node

const minApiVersion = '1.1.0'

const { logger, getSymbol } = require('./lib/logger')
const config = require('./lib/args')
if (!config) {
Expand Down Expand Up @@ -58,15 +60,25 @@ async function run() {
}
}

async function preflightServices () {
try {
await auth.getToken()
logger.info({ component: 'main', message: `preflight token request suceeded`})
await api.getCollectionAssets(config.collectionId)
await api.getInstalledStigs()
logger.info({ component: 'main', message: `prefilght api requests suceeded`})
async function hasMinApiVersion () {
const semverGte = require('semver/functions/gte')
const [remoteApiVersion] = await api.getDefinition('$.info.version')
logger.info({ component: 'main', message: `preflight API version`, minApiVersion, remoteApiVersion})
if (semverGte(remoteApiVersion, minApiVersion)) {
return true
}
else {
throw( `Remote API version ${remoteApiVersion} is not compatible with this release.` )
}
finally {}
}

async function preflightServices () {
await hasMinApiVersion()
await auth.getToken()
logger.info({ component: 'main', message: `preflight token request suceeded`})
await api.getCollectionAssets(config.collectionId)
await api.getInstalledStigs()
logger.info({ component: 'main', message: `prefilght api requests suceeded`})
}

function getObfuscatedConfig (config) {
Expand Down
36 changes: 36 additions & 0 deletions lib/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,42 @@ const config = require('./args')
const auth = require('./auth')
const { logger, getSymbol } = require('./logger')

module.exports.getDefinition = async function (jsonPath) {
let response
try {
response = await got.get(`${config.api}/op/definition${jsonPath ? '?jsonpath=' + encodeURIComponent(jsonPath) : ''}`, {
responseType: 'json'
})
logResponse (response )
return response.body
}
catch (e) {
e.component = 'api'
e.message = 'query failed'
throw (e)
}
}

module.exports.getCollection = async function (collectionId) {
let response
try {
await auth.getToken()
response = await got.get(`${config.api}/collections/${collectionId}`, {
headers: {
Authorization: `Bearer ${auth.tokens.access_token}`
},
responseType: 'json'
})
logResponse (response )
return response.body
}
catch (e) {
e.component = 'api'
e.message = 'query failed'
throw (e)
}
}

module.exports.getCollectionAssets = async function (collectionId) {
let response
try {
Expand Down
127 changes: 73 additions & 54 deletions lib/cargo.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const component = 'cargo'
let batchId = 0

class TaskObject {
constructor ( { apiAssets = [], apiStigs = [], parsedResults = [] } ) {
constructor({ apiAssets = [], apiStigs = [], parsedResults = [] }) {
// An array of results from the parsers
this.parsedResults = parsedResults

Expand All @@ -17,10 +17,10 @@ class TaskObject {
// Create Maps of the assets by assetName and metadata.cklHostName
this.mappedAssetNames = new Map()
this.mappedCklHostnames = new Map()
for ( const apiAsset of apiAssets ) {
for (const apiAsset of apiAssets) {
// Update .stigs to an array of benchmarkId strings
apiAsset.stigs = apiAsset.stigs.map( stig => stig.benchmarkId )
this.mappedAssetNames.set( apiAsset.name.toLowerCase(), apiAsset )
apiAsset.stigs = apiAsset.stigs.map(stig => stig.benchmarkId)
this.mappedAssetNames.set(apiAsset.name.toLowerCase(), apiAsset)
if (apiAsset.metadata?.cklHostName) {
const v = this.mappedCklHostnames.get(apiAsset.metadata.cklHostName.toLowerCase())
if (v) {
Expand All @@ -35,8 +35,8 @@ class TaskObject {
// A Map() of the installed benchmarkIds return by the API
// key: benchmarkId, value: array of revisionStr
this.mappedStigs = new Map()
for ( const apiStig of apiStigs ) {
this.mappedStigs.set( apiStig.benchmarkId, apiStig.revisionStrs )
for (const apiStig of apiStigs) {
this.mappedStigs.set(apiStig.benchmarkId, apiStig.revisionStrs)
}

// An array of accumulated errors
Expand All @@ -46,46 +46,47 @@ class TaskObject {
this.taskAssets = this._createTaskAssets()
}

_findAssetFromParsedTarget( target ) {
_findAssetFromParsedTarget(target) {
if (!target.metadata.cklHostName) {
return this.mappedAssetNames.get(target.name.toLowerCase())
}
const matchedByCklHostname = this.mappedCklHostnames.get(target.metadata.cklHostName.toLowerCase())
if (!matchedByCklHostname) return null
const matchedByAllCklMetadata = matchedByCklHostname.find(
const matchedByAllCklMetadata = matchedByCklHostname.find(
asset => asset.metadata.cklWebDbInstance === target.metadata.cklWebDbInstance
&& asset.metadata.cklWebDbSite === target.metadata.cklWebDbSite)
&& asset.metadata.cklWebDbSite === target.metadata.cklWebDbSite)
if (!matchedByAllCklMetadata) return null
return matchedByAllCklMetadata
}

_createTaskAssets() {
// taskAssets is a Map() keyed by mapKey, the values are
// taskAssets is a Map() keyed by lowercase asset name (or CKL metadata), the value is an object:
// {
// newAsset: false, // does the asset need to be created?
// assetProps: parseResult.target, // asset properties from the parsed results
// hasNewBenchmarkIds: false, // are there new STIG assignments?
// stigsIgnored: [], // benchmarkIds ignored because no updates allowed
// reviews: [] // the reviews to be posted
// knownAsset: false, // does the asset need to be created
// assetProps: null, // an Asset object suitable for put/post to the API
// hasNewAssignment: false, // are there new STIG assignments?
// newAssignments: [], // any new assignments
// checklists: new Map(), // the vetted result checklists, a Map() keyed by benchmarkId
// checklistsIgnored: [], // the ignored checklists
// reviews: [] // the vetted reviews
// }


const taskAssets = new Map()

for (const parsedResult of this.parsedResults) {
// Generate mapping key
let mapKey, tMeta = parsedResult.target.metadata
if (!tMeta.cklHostName) {
mapKey = parsedResult.target.name.toLowerCase()
}
else {
const appends = [tMeta.cklHostName]
appends.push(tMeta.cklWebDbSite ?? 'NA')
appends.push(tMeta.cklWebDbInstance ?? 'NA')
mapKey = appends.join('-')
mapKey = `${tMeta.cklHostName}-${tMeta.cklWebDbSite ?? 'NA'}-${tMeta.cklWebDbInstance ?? 'NA'}`
}

// Try to find the asset in the API response
const apiAsset = this._findAssetFromParsedTarget( parsedResult.target )
if (! apiAsset && ! config.createObjects) {
const apiAsset = this._findAssetFromParsedTarget(parsedResult.target)
if (!apiAsset && !config.createObjects) {
// Bail if the asset doesn't exist and we won't create it
this.errors.push({
file: parsedResult.file,
Expand All @@ -102,19 +103,20 @@ class TaskObject {
continue
}
// Try to find the target in our Map()
let taskAsset = taskAssets.get( mapKey )
let taskAsset = taskAssets.get(mapKey)

if ( !taskAsset ) {
if (!taskAsset) {
// This is our first encounter with this assetName, initialize Map() value
taskAsset = {
knownAsset: false,
assetProps: null, // an object suitable for put/post to the API
hasNewAssignment: false,
checklists: [], // the vetted result checklists
newAssignments: [],
checklists: new Map(), // the vetted result checklists
checklistsIgnored: [], // the ignored checklists
reviews: [] // the vetted reviews
}
if ( !apiAsset ) {
}
if (!apiAsset) {
// The asset does not exist in the API. Set assetProps from this parseResult.
if (!tMeta.cklHostName) {
taskAsset.assetProps = { ...parsedResult.target, collectionId: config.collectionId, stigs: [] }
Expand All @@ -129,71 +131,86 @@ class TaskObject {
taskAsset.assetProps = apiAsset
}
// Insert the asset into taskAssets
taskAssets.set( mapKey, taskAsset )
taskAssets.set(mapKey, taskAsset)
}

// Helper functions
const stigIsInstalled = ( { benchmarkId, revisionStr } ) => {
const revisionStrs = this.mappedStigs.get( benchmarkId )
if ( revisionStrs ) {
return revisionStr && config.strictRevisionCheck ? revisionStrs.includes( revisionStr ) : true
const stigIsInstalled = ({ benchmarkId, revisionStr }) => {
const revisionStrs = this.mappedStigs.get(benchmarkId)
if (revisionStrs) {
return revisionStr && config.strictRevisionCheck ? revisionStrs.includes(revisionStr) : true
}
else {
return false
}
}
const stigIsAssigned = ( { benchmarkId } ) => {
return taskAsset.assetProps.stigs.includes( benchmarkId )
const stigIsAssigned = ({ benchmarkId }) => {
return taskAsset.assetProps.stigs.includes(benchmarkId)
}
const assignStig = ( benchmarkId ) => {
if (!stigIsAssigned( benchmarkId )) {
const assignStig = (benchmarkId) => {
if (!stigIsAssigned(benchmarkId)) {
taskAsset.hasNewAssignment = true
taskAsset.assetProps.stigs.push( benchmarkId )
taskAsset.newAssignments.push(benchmarkId)
taskAsset.assetProps.stigs.push(benchmarkId)
}
}
const stigIsNewlyAssigned = (benchmarkId) => taskAsset.newAssignments.includes(benchmarkId)

const addToTaskAssetChecklistMapArray = (taskAsset, checklist) => {
let checklistArray = taskAsset.checklists.get(checklist.benchmarkId)
if (checklistArray) {
checklistArray.push(checklist)
}
else {
taskAsset.checklists.set(checklist.benchmarkId, [checklist])
}
}


// Vet the checklists in this parseResult
for (const checklist of parsedResult.checklists) {
checklist.file = parsedResult.file
if ( stigIsInstalled( checklist ) ) {
if ( stigIsAssigned( checklist ) ) {
taskAsset.checklists.push( checklist )
if (stigIsInstalled(checklist)) {
if (stigIsAssigned(checklist)) {
checklist.newAssignment = stigIsNewlyAssigned(checklist.benchmarkId)
addToTaskAssetChecklistMapArray(taskAsset, checklist)
logger.debug({
component: 'taskobject',
message: 'checklist included',
file: parsedResult.file,
assetName: parsedResult.target.name,
benchmarkId: checklist.benchmarkId,
})
})
}
else if ( config.createObjects ) {
assignStig( checklist.benchmarkId )
taskAsset.checklists.push( checklist )
else if (config.createObjects) {
assignStig(checklist.benchmarkId)
checklist.newAssignment = true
addToTaskAssetChecklistMapArray(taskAsset, checklist)
logger.debug({
component: 'taskobject',
message: 'checklist assigned and included',
file: parsedResult.file,
assetName: parsedResult.target.name,
benchmarkId: checklist.benchmarkId,
})
})

}
else {
checklist.ignored = `STIG is not assigned`
taskAsset.checklistsIgnored.push( checklist )
checklist.ignored = `Not mapped to Asset`
taskAsset.checklistsIgnored.push(checklist)
logger.warn({
component: 'taskobject',
message: 'checklist ignored',
file: parsedResult.file,
assetName: parsedResult.target.name,
benchmarkId: checklist.benchmarkId,
reason: 'stig is not assigned'
})
})
}
}
else {
checklist.ignored = `STIG is not installed`
taskAsset.checklistsIgnored.push( checklist )
checklist.ignored = `Not installed`
taskAsset.checklistsIgnored.push(checklist)
logger.warn({
component: 'taskobject',
message: 'checklist ignored',
Expand Down Expand Up @@ -243,7 +260,10 @@ async function writer ( taskAsset ) {

// POST reviews
let reviews = []
for (const checklist of taskAsset.checklists) {
for (const assetStigChecklists of taskAsset.checklists.values()) {
// Since the parsed files were sorted by ascending date order, the last
// item in each checklists array was parsed from the most recently dated checklist file and we will choose this item.
const checklist = assetStigChecklists.slice(-1)[0]
reviews = reviews.concat(checklist.reviews)
}
if (reviews.length > 0) {
Expand All @@ -252,9 +272,8 @@ async function writer ( taskAsset ) {
component: component,
message: `posted reviews`,
asset: { name: taskAsset.assetProps.name, id: taskAsset.assetProps.assetId },
permitted: r.permitted.length,
rejected: r.rejected.length,
errors: r.errors.length
rejected: r.rejected,
affected: r.affected
})
}
else {
Expand Down
Loading

0 comments on commit 351f6ef

Please sign in to comment.