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

Add Support for USMC MCCAST formatted POAM export in STIG Manager #1345

Merged
merged 17 commits into from
Aug 28, 2024
Merged
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
11 changes: 11 additions & 0 deletions .github/workflows/api-audit-test-coverage-response.yml
Original file line number Diff line number Diff line change
Expand Up @@ -191,18 +191,29 @@ jobs:
needs: test_api
runs-on: ubuntu-latest
steps:
- name: Check if PR is from a fork
id: check_fork
run: |
if [ "${{ github.event.pull_request.head.repo.fork }}" == "true" ]; then
echo "This is a PR from a fork, skipping sonarcloud analysis."
echo "SKIP_STEP=true" >> $GITHUB_ENV
fi
- name: Checkout repository
if: env.SKIP_STEP != 'true'
uses: actions/checkout@v4
with:
fetch-depth: 0 # Important to fetch all history for accurate blame information
- name: Download lcov artifact
if: env.SKIP_STEP != 'true'
uses: actions/download-artifact@v4
with:
name: coverage-report
- name: Move lcov.info to api/source
if: env.SKIP_STEP != 'true'
run: mv lcov.info ./api/source/

- name: Analyze API with SonarCloud
if: env.SKIP_STEP != 'true'
uses: SonarSource/sonarcloud-github-action@v2.0.2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information
Expand Down
10 changes: 10 additions & 0 deletions .github/workflows/client-sonarcloud.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,22 @@ jobs:
name: SonarCloud Analysis client
runs-on: ubuntu-latest
steps:
- name: Check if PR is from a fork
id: check_fork
run: |
if [ "${{ github.event.pull_request.head.repo.fork }}" == "true" ]; then
echo "This is a PR from a fork, skipping sonarcloud analysis."
echo "SKIP_STEP=true" >> $GITHUB_ENV
fi
#checkout the repo
- name: Checkout repository
if: env.SKIP_STEP != 'true'
uses: actions/checkout@v4
with:
fetch-depth: 0 # Important to fetch all history for accurate blame information

- name: Analyze client with SonarCloud
if: env.SKIP_STEP != 'true'
uses: SonarSource/sonarcloud-github-action@v2.0.2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information
Expand Down
1 change: 1 addition & 0 deletions CONTRIBUTORS.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
- Copyright 2021 Russell Johnson, russell.d.johnson@saic.com
- Copyright 2023-2024 Mathew Ferreira, mferreira@rite-solutions.com
- Copyright 2024 Rajesh Shrestha, rshrestha@rite-solutions.com
- Copyright 2024 David Whalen, david.whalen@usmc.mil
- _Add the copyright date, your name, and email address here. (PLEASE KEEP THIS LINE)_

## Note for U.S. Federal Employees
Expand Down
40 changes: 26 additions & 14 deletions api/source/controllers/Collection.js
Original file line number Diff line number Diff line change
Expand Up @@ -151,36 +151,48 @@ module.exports.getFindingsByCollection = async function getFindingsByCollection

module.exports.getPoamByCollection = async function getFindingsByCollection (req, res, next) {
try {
const aggregator = req.query.aggregator
const benchmarkId = req.query.benchmarkId
const assetId = req.query.assetId
const acceptedOnly = req.query.acceptedOnly
const {
aggregator,
benchmarkId,
assetId,
acceptedOnly,
date,
office,
status,
mccastPackageId,
mccastAuthName,
format
} = req.query
const defaults = {
date: req.query.date,
office: req.query.office,
status: req.query.status
date,
office,
status,
mccastPackageId,
mccastAuthName
}
const { collectionId, collectionGrant } = getCollectionInfoAndCheckPermission(req, Security.ACCESS_LEVEL.Restricted)
const response = await CollectionService.getFindingsByCollection( collectionId, aggregator, benchmarkId, assetId, acceptedOnly,
const findings = await CollectionService.getFindingsByCollection( collectionId, aggregator, benchmarkId, assetId, acceptedOnly,
[
'rulesWithDiscussion',
'groups',
'assets',
'stigs',
'ccis'
], req.userObject )

const po = Serialize.poamObjectFromFindings(response, defaults)
const xlsx = await Serialize.xlsxFromPoamObject(po)
let collectionName = collectionGrant.collection.name
writer.writeInlineFile( res, xlsx, `POAM-${collectionName}_${escape.filenameComponentFromDate()}.xlsx`, 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')

const poFns = {
EMASS: Serialize.poamObjectFromFindings,
MCCAST: Serialize.mccastPoamObjectFromFindings
}
const xlsx = await Serialize.xlsxFromPoamObject(poFns[format](findings, defaults), format)
writer.writeInlineFile( res, xlsx, `POAM-${format}-${collectionGrant.collection.name}_${escape.filenameComponentFromDate()}.xlsx`, 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')

}
catch (err) {
next(err)
}
}


module.exports.getStigAssetsByCollectionUser = async function getStigAssetsByCollectionUser (req, res, next) {
try {
const userId = req.params.userId
Expand Down
18 changes: 9 additions & 9 deletions api/source/package-lock.json

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

2 changes: 1 addition & 1 deletion api/source/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"dependencies": {
"archiver": "^5.3.1",
"async-retry": "^1.3.1",
"axios": "^1.6.1",
"axios": "^1.7.4",
"compression": "^1.7.4",
"cors": "^2.8.5",
"csv-stringify": "^6.2.0",
Expand Down
29 changes: 29 additions & 0 deletions api/source/specification/stig-manager.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2418,17 +2418,36 @@ paths:
in: query
schema:
type: string
pattern: '^(0[1-9]|1[0-2])/(0[1-9]|[12][0-9]|3[01])/\d{4}$'
- name: office
description: Value for column Office/Org
in: query
schema:
type: string
maxLength: 255
allowReserved: true
- name: status
description: Value for column Status
in: query
schema:
type: string
maxLength: 255
allowReserved: true
- $ref: '#/components/parameters/PoamFormatQuery'
- name: mccastPackageId
description: Value for POAM MCCAST PackageId
in: query
schema:
type: string
maxLength: 255
allowReserved: true
- name: mccastAuthName
description: Value for POAM MCCAST Authorization Name
in: query
schema:
type: string
maxLength: 255
allowReserved: true
responses:
'200':
description: CollectionFinding response
Expand Down Expand Up @@ -7558,6 +7577,16 @@ components:
enum:
- ruleId
- groupId
PoamFormatQuery:
name: format
in: query
description: Value for POAM format (ie. EMASS, MCCAST)
schema:
type: string
enum:
- EMASS
- MCCAST
default: EMASS
RetentionDateQuery:
name: retentionDate
in: query
Expand Down
Binary file added api/source/utils/poam-template-mccast.xlsx
Binary file not shown.
46 changes: 39 additions & 7 deletions api/source/utils/serializers.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,36 @@ const {promises: fs} = require('fs')
const path = require('path')
const XlsxTemplate = require('xlsx-template')

module.exports.poamObjectFromFindings = function ( findings, defaults = {} ) {
module.exports.mccastPoamObjectFromFindings = function (findings, defaults = {}) {
const vuln = findings.map( finding => ({
authPackage: defaults.mccastAuthName,
name: finding.rules[0].title,
dateId: finding.stigs[0].ruleCount === 0 ? finding.stigs[0].benchmarkDate : '',
stigInfo: 'STIG Finding',
status: defaults.status,
packageId: defaults.mccastPackageId,
date: defaults.date,
startDate: '',
endDate: '',
securityChecks: finding.rules[0].ruleId || finding.groupId,
control: finding.ccis.map( cci => `DoD RMF-${defaults.mccastPackageId}-${cci.apAcronym?.replace(/\./g,' ')}-CNSSI 1253`).join('\n'),
resultingRisk: finding.severity === 'medium' ? 'Moderate' : `${finding.severity.charAt(0).toUpperCase()}${finding.severity.slice(1)}`,
weakness: finding.rules[0].vulnDiscussion,
mitigations: '',
comments: finding.stigs[0].ruleCount === 0 ? '' : finding.ccis.map( cci => `CCI-${cci.cci}`).join('\n'),
assets: finding.assets.map( asset => asset.name ).join('\n'),
mav: '',
mac: '',
mpr: '',
mui: '',
ms: '',
mi: '',
ma: ''
}))
return {vuln}
}

module.exports.poamObjectFromFindings = function (findings, defaults = {}) {
const vuln = findings.map( finding => ({
desc: `Title:\n${finding.rules[0].title}\n\nDescription:\n${finding.rules[0].vulnDiscussion}`,
control: finding.ccis.map( cci => cci.apAcronym).join('\n'),
Expand Down Expand Up @@ -30,15 +59,18 @@ module.exports.poamObjectFromFindings = function ( findings, defaults = {} ) {
recommendations: '',
resultingRisk: finding.severity === 'medium' ? 'Moderate' : `${finding.severity.charAt(0).toUpperCase()}${finding.severity.slice(1)}`,
}))
return {
vuln: vuln
}
return {vuln}
}

module.exports.xlsxFromPoamObject = async function ( po ) {
const templateData = await fs.readFile(path.join(__dirname,'poam-template.xlsx'))

module.exports.xlsxFromPoamObject = async function (substitutions, format) {
const templateFiles = {
EMASS: 'poam-template.xlsx',
MCCAST: 'poam-template-mccast.xlsx'
}
const templateData = await fs.readFile(path.join(__dirname, templateFiles[format]))
const template = new XlsxTemplate()
await template.loadTemplate(templateData)
await template.substitute( 1, po )
await template.substitute(1, substitutions)
return await template.generate({type: 'nodebuffer'})
}
Loading
Loading