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

feat: include project policy related compliance checks #195

Merged
merged 11 commits into from
Feb 12, 2025
Merged
146 changes: 146 additions & 0 deletions __tests__/checks/genericProjectPolicyComplianceCheck.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
const knexInit = require('knex')
const { getConfig } = require('../../src/config')

const complianceChecks = require('../../src/checks')
const {
resetDatabase, initializeStore
} = require('../../__utils__')

const { sampleGithubOrg } = require('../../__fixtures__')

const { dbSettings } = getConfig('test')

let knex
let check

let addProject,
getAllResults,
getAllTasks,
getAllAlerts,
addAlert,
addTask,
addResult,
getCheckByCodeName

beforeAll(async () => {
knex = knexInit(dbSettings);
({
addProject,
getAllResults,
getAllTasks,
getAllAlerts,
addAlert,
addTask,
addResult,
getCheckByCodeName
} = initializeStore(knex))
check = await getCheckByCodeName('defineFunctionalRoles')
})

beforeEach(async () => {
await resetDatabase(knex)
})

afterAll(async () => {
await knex.destroy()
})

describe('Integration: Generic Project Policy Compliance Checks', () => {
// @TODO: ensure that the genericProjectPolicyComplianceCheck is used in the complianceChecks
it('Should include all the generic Project policy compliance checks', () => {
expect(complianceChecks).toHaveProperty('defineFunctionalRoles')
expect(complianceChecks).toHaveProperty('orgToolingMFA')
expect(complianceChecks).toHaveProperty('softwareArchitectureDocs')
expect(complianceChecks).toHaveProperty('MFAImpersonationDefense')
expect(complianceChecks).toHaveProperty('includeCVEInReleaseNotes')
expect(complianceChecks).toHaveProperty('assignCVEForKnownVulns')
expect(complianceChecks).toHaveProperty('incidentResponsePlan')
expect(complianceChecks).toHaveProperty('regressionTestsForVulns')
expect(complianceChecks).toHaveProperty('vulnResponse14Days')
expect(complianceChecks).toHaveProperty('useCVDToolForVulns')
expect(complianceChecks).toHaveProperty('securityMdMeetsOpenJSCVD')
expect(complianceChecks).toHaveProperty('consistentBuildProcessDocs')
expect(complianceChecks).toHaveProperty('machineReadableDependencies')
expect(complianceChecks).toHaveProperty('identifyModifiedDependencies')
expect(complianceChecks).toHaveProperty('ciAndCdPipelineAsCode')
expect(complianceChecks).toHaveProperty('npmOrgMFA')
expect(complianceChecks).toHaveProperty('npmPublicationMFA')
expect(complianceChecks).toHaveProperty('upgradePathDocs')
})

test('Should add results without alerts or tasks', async () => {
// Add a passed check scenario
await addProject({ name: sampleGithubOrg.login, has_defineFunctionalRoles_policy: true })
// Check that the database is empty
let results = await getAllResults()
expect(results.length).toBe(0)
let alerts = await getAllAlerts()
expect(alerts.length).toBe(0)
let tasks = await getAllTasks()
expect(tasks.length).toBe(0)
// Run the check
await expect(complianceChecks.defineFunctionalRoles(knex)).resolves.toBeUndefined()
// Check that the database has the expected results
results = await getAllResults()
expect(results.length).toBe(1)
expect(results[0].status).toBe('passed')
expect(results[0].compliance_check_id).toBe(check.id)
alerts = await getAllAlerts()
expect(alerts.length).toBe(0)
tasks = await getAllTasks()
expect(tasks.length).toBe(0)
})

test('Should delete (previous alerts and tasks) and add results', async () => {
// Prepare the Scenario
const project = await addProject({ name: sampleGithubOrg.login, has_defineFunctionalRoles_policy: true })
await addAlert({ compliance_check_id: check.id, project_id: project.id, title: 'existing', description: 'existing', severity: 'critical' })
await addTask({ compliance_check_id: check.id, project_id: project.id, title: 'existing', description: 'existing', severity: 'critical' })
// Check that the database has the expected results
let results = await getAllResults()
expect(results.length).toBe(0)
let alerts = await getAllAlerts()
expect(alerts.length).toBe(1)
expect(alerts[0].compliance_check_id).toBe(check.id)
let tasks = await getAllTasks()
expect(tasks.length).toBe(1)
expect(tasks[0].compliance_check_id).toBe(check.id)
// Run the check
await complianceChecks.defineFunctionalRoles(knex)
// Check that the database has the expected results
results = await getAllResults()
expect(results.length).toBe(1)
expect(results[0].status).toBe('passed')
alerts = await getAllAlerts()
expect(alerts.length).toBe(0)
tasks = await getAllTasks()
expect(tasks.length).toBe(0)
})

test('Should add (alerts and tasks) and update results', async () => {
// Prepare the Scenario
const project = await addProject({ name: sampleGithubOrg.login, has_defineFunctionalRoles_policy: false })
await addResult({ compliance_check_id: check.id, project_id: project.id, status: 'passed', rationale: 'failed previously', severity: 'critical' })
// Check that the database has the expected results
let results = await getAllResults()
expect(results.length).toBe(1)
expect(results[0].compliance_check_id).toBe(check.id)
let alerts = await getAllAlerts()
expect(alerts.length).toBe(0)
let tasks = await getAllTasks()
expect(tasks.length).toBe(0)
// Run the check
await complianceChecks.defineFunctionalRoles(knex)
// Check that the database has the expected results
results = await getAllResults()
expect(results.length).toBe(1)
expect(results[0].status).toBe('failed')
expect(results[0].rationale).not.toBe('failed previously')
alerts = await getAllAlerts()
expect(alerts.length).toBe(1)
expect(alerts[0].compliance_check_id).toBe(check.id)
tasks = await getAllTasks()
expect(tasks.length).toBe(1)
expect(tasks[0].compliance_check_id).toBe(check.id)
})
})
134 changes: 134 additions & 0 deletions __tests__/checks/validators/genericProjectPolicyValidator.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
const validators = require('../../../src/checks/validators')

describe('Generic Project Policy Validators', () => {
let check, projects

beforeEach(() => {
projects = [
{
id: 1,
has_defineFunctionalRoles_policy: true
}, {
id: 2,
has_defineFunctionalRoles_policy: true
}]

check = {
id: 1,
default_priority_group: 'P2',
details_url: 'https://example.com'
}
})

// @TODO: ensure that the genericProjectPolicyValidator is used in the validators
it('Should include all the generic validators', () => {
expect(validators).toHaveProperty('defineFunctionalRoles')
expect(validators).toHaveProperty('orgToolingMFA')
expect(validators).toHaveProperty('softwareArchitectureDocs')
expect(validators).toHaveProperty('MFAImpersonationDefense')
expect(validators).toHaveProperty('includeCVEInReleaseNotes')
expect(validators).toHaveProperty('assignCVEForKnownVulns')
expect(validators).toHaveProperty('incidentResponsePlan')
expect(validators).toHaveProperty('regressionTestsForVulns')
expect(validators).toHaveProperty('vulnResponse14Days')
expect(validators).toHaveProperty('useCVDToolForVulns')
expect(validators).toHaveProperty('securityMdMeetsOpenJSCVD')
expect(validators).toHaveProperty('consistentBuildProcessDocs')
expect(validators).toHaveProperty('machineReadableDependencies')
expect(validators).toHaveProperty('identifyModifiedDependencies')
expect(validators).toHaveProperty('ciAndCdPipelineAsCode')
expect(validators).toHaveProperty('npmOrgMFA')
expect(validators).toHaveProperty('npmPublicationMFA')
expect(validators).toHaveProperty('upgradePathDocs')
})

it('Should generate a passed result if all the projects subscribes the policy', () => {
const analysis = validators.defineFunctionalRoles({ check, projects })
expect(analysis).toEqual({
alerts: [],
results: [
{
project_id: 1,
compliance_check_id: 1,
severity: 'critical',
status: 'passed',
rationale: 'The project subscribes to the defineFunctionalRoles policy'
},
{
compliance_check_id: 1,
project_id: 2,
rationale: 'The project subscribes to the defineFunctionalRoles policy',
severity: 'critical',
status: 'passed'
}
],
tasks: []
})
})

it('Should generate a failed result if any project does not subscribes the policy', () => {
projects[0].has_defineFunctionalRoles_policy = false
const analysis = validators.defineFunctionalRoles({ check, projects })
expect(analysis).toEqual({
alerts: [
{
project_id: 1,
compliance_check_id: 1,
severity: 'critical',
title: 'The project does not subscribe to the defineFunctionalRoles policy',
description: 'Check the details on https://example.com'
}
],
results: [
{
project_id: 1,
compliance_check_id: 1,
severity: 'critical',
status: 'failed',
rationale: 'The project does not subscribe to the defineFunctionalRoles policy'
},
{
compliance_check_id: 1,
project_id: 2,
rationale: 'The project subscribes to the defineFunctionalRoles policy',
severity: 'critical',
status: 'passed'
}
],
tasks: [
{
project_id: 1,
compliance_check_id: 1,
severity: 'critical',
title: 'Subscribe to the defineFunctionalRoles policy',
description: 'Check the details on https://example.com'
}
]
})
})

it('Should generate an unknown result if any project has an unknown policy status', () => {
projects[0].has_defineFunctionalRoles_policy = null
const analysis = validators.defineFunctionalRoles({ check, projects })
expect(analysis).toEqual({
alerts: [],
results: [
{
project_id: 1,
compliance_check_id: 1,
severity: 'critical',
status: 'unknown',
rationale: 'The project has the policy defineFunctionalRoles with an unknown status'
},
{
compliance_check_id: 1,
project_id: 2,
rationale: 'The project subscribes to the defineFunctionalRoles policy',
severity: 'critical',
status: 'passed'
}
],
tasks: []
})
})
})
31 changes: 31 additions & 0 deletions src/checks/complianceChecks/genericProjectPolicyComplianceCheck.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
const validators = require('../validators')
const debugInstance = require('debug')
const { initializeStore } = require('../../store')

module.exports = (checkName) => async (knex, { projects } = {}) => {
const debug = debugInstance(`checks:${checkName}`)
const {
getCheckByCodeName,
getAllProjects, addAlert, addTask, upsertComplianceCheckResult,
deleteAlertsByComplianceCheckId, deleteTasksByComplianceCheckId
} = initializeStore(knex)
debug('Collecting relevant data...')
const check = await getCheckByCodeName(checkName)
if (!projects || (Array.isArray(projects) && projects.length === 0)) {
projects = await getAllProjects()
}

debug('Extracting the validation results...')
const analysis = validators[checkName]({ projects, check })

debug('Deleting previous alerts and tasks to avoid orphaned records...')
await deleteAlertsByComplianceCheckId(check.id)
await deleteTasksByComplianceCheckId(check.id)

debug('Upserting the new results...')
await Promise.all(analysis.results.map(result => upsertComplianceCheckResult(result)))

debug('Inserting the new Alerts and Tasks...')
await Promise.all(analysis.alerts.map(alert => addAlert(alert)))
await Promise.all(analysis.tasks.map(task => addTask(task)))
}
25 changes: 24 additions & 1 deletion src/checks/index.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
const { readdirSync } = require('fs')
const { join } = require('path')
const debug = require('debug')('checks:index')
const genericProjectPolicyComplianceCheck = require('./complianceChecks/genericProjectPolicyComplianceCheck')

// This will load all the files in the complianceChecks directory and export them as an object. It works similar to require-all
debug('Loading compliance check files...')
const checksPath = join(__dirname, 'complianceChecks')
const files = readdirSync(checksPath)
const jsFiles = files.filter(file => file.endsWith('.js'))
const jsFiles = files.filter(file => file.endsWith('.js') && file !== 'genericProjectPolicyComplianceCheck.js')
const checks = {}
for (const file of jsFiles) {
debug(`Loading ${file}...`)
Expand All @@ -17,4 +18,26 @@ for (const file of jsFiles) {

debug('Checks files loaded')

// Generic Policies
checks.defineFunctionalRoles = genericProjectPolicyComplianceCheck('defineFunctionalRoles')
checks.orgToolingMFA = genericProjectPolicyComplianceCheck('orgToolingMFA')
checks.softwareArchitectureDocs = genericProjectPolicyComplianceCheck('softwareArchitectureDocs')
checks.MFAImpersonationDefense = genericProjectPolicyComplianceCheck('MFAImpersonationDefense')
checks.includeCVEInReleaseNotes = genericProjectPolicyComplianceCheck('includeCVEInReleaseNotes')
checks.assignCVEForKnownVulns = genericProjectPolicyComplianceCheck('assignCVEForKnownVulns')
checks.incidentResponsePlan = genericProjectPolicyComplianceCheck('incidentResponsePlan')
checks.regressionTestsForVulns = genericProjectPolicyComplianceCheck('regressionTestsForVulns')
checks.vulnResponse14Days = genericProjectPolicyComplianceCheck('vulnResponse14Days')
checks.useCVDToolForVulns = genericProjectPolicyComplianceCheck('useCVDToolForVulns')
checks.securityMdMeetsOpenJSCVD = genericProjectPolicyComplianceCheck('securityMdMeetsOpenJSCVD')
checks.consistentBuildProcessDocs = genericProjectPolicyComplianceCheck('consistentBuildProcessDocs')
checks.machineReadableDependencies = genericProjectPolicyComplianceCheck('machineReadableDependencies')
checks.identifyModifiedDependencies = genericProjectPolicyComplianceCheck('identifyModifiedDependencies')
checks.ciAndCdPipelineAsCode = genericProjectPolicyComplianceCheck('ciAndCdPipelineAsCode')
checks.npmOrgMFA = genericProjectPolicyComplianceCheck('npmOrgMFA')
checks.npmPublicationMFA = genericProjectPolicyComplianceCheck('npmPublicationMFA')
checks.upgradePathDocs = genericProjectPolicyComplianceCheck('upgradePathDocs')

debug('Generic Policies loaded')

module.exports = checks
Loading