Skip to content
Open
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
4 changes: 2 additions & 2 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
@@ -1,6 +1,6 @@
{
"name": "stig-management-api",
"version": "1.6.8",
"version": "1.6.9",
"description": "An API for managing evaluations of Security Technical Implementation Guide (STIG) assessments.",
"main": "index.js",
"scripts": {
Expand Down
18 changes: 18 additions & 0 deletions release-notes.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,21 @@
1.6.9
-------

Changes:

- (API) Added guard to prevent elevated requests from modifying collection ``settings``, ``labels``, or ``metadata`` on create/replace/update
- (API) Simplified asset collection retrieval in controllers
- (API) Refactored JWKS cache error logging
- (API) Replaced direct string interpolation in SQL query construction with parameterized binds in MetricsService and JobService
- (UI) New ``STIGMAN_CLIENT_CONSOLE_MODE`` environment variable to suppress console output in non-development environments
- (UI) Various escaping and DOM insertion improvements across multiple SM components
- (UI) Updated OIDC worker initialization
- (Docs) Clarified data and permissions documentation for elevated actions
- (Tests) Added regression tests for cross-collection write access; updated test utilities and collection test fixtures to align with API behavior
- (Dependencies) Update ``fast-xml-parser`` to v5.7.1 and remove the ``uuid`` runtime dependency from the API
- (Dependencies) Update ``@nuwcdivnpt/stig-manager-client-modules`` to v1.6.7
- (Dependencies) Various security and maintenance updates

1.6.8
-------

Expand Down
61 changes: 35 additions & 26 deletions test/state/mocha/bootstrap.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@ describe('Boot with no dependencies', function () {
})

after(async function () {
await api.stop()
addContext(this, {title: 'api-log', value: api.logRecords})
this.timeout(60000)
if (api) await api.stop().catch(() => {})
if (api) addContext(this, {title: 'api-log', value: api.logRecords})
})

describe('GET /op/state', function () {
Expand Down Expand Up @@ -113,10 +114,11 @@ describe('Boot with both dependencies', function () {
})

after(async function () {
await api.stop()
await mysql.stop()
await oidc.stop()
addContext(this, {title: 'api-log', value: api.logRecords})
this.timeout(60000)
if (api) await api.stop().catch(() => {})
if (mysql) await mysql.stop().catch(() => {})
if (oidc) await oidc.stop().catch(() => {})
if (api) addContext(this, {title: 'api-log', value: api.logRecords})
})

describe('GET /op/state', function () {
Expand All @@ -127,7 +129,7 @@ describe('Boot with both dependencies', function () {
expect(res.body.dependencies).to.eql({db: true, oidc: true})
})
})

describe('GET /op/configuration', function () {
it('should return 200 when dependencies are available', async function () {
const res = await simpleRequest(`${apiOrigin}/api/op/configuration`)
Expand Down Expand Up @@ -189,10 +191,11 @@ describe('Boot with old mysql', function () {
})

after(async function () {
await api.stop()
await mysql.stop()
await oidc.stop()
addContext(this, {title: 'api-log', value: api.logRecords})
this.timeout(60000)
if (api) await api.stop().catch(() => {})
if (mysql) await mysql.stop().catch(() => {})
if (oidc) await oidc.stop().catch(() => {})
if (api) addContext(this, {title: 'api-log', value: api.logRecords})
})

describe('exit code', function () {
Expand Down Expand Up @@ -249,10 +252,11 @@ describe('Boot with insecure kid - allow insecure tokens false', function () {
})

after(async function () {
await api.stop()
await mysql.stop()
await oidc.stop()
addContext(this, {title: 'api-log', value: api.logRecords})
this.timeout(60000)
if (api) await api.stop().catch(() => {})
if (mysql) await mysql.stop().catch(() => {})
if (oidc) await oidc.stop().catch(() => {})
if (api) addContext(this, {title: 'api-log', value: api.logRecords})
})

describe('exit code', function () {
Expand Down Expand Up @@ -312,10 +316,11 @@ describe('Boot without insecure kid - request with insecure token' , function ()
})

after(async function () {
await api.stop()
await mysql.stop()
await oidc.stop()
addContext(this, {title: 'api-log', value: api.logRecords})
this.timeout(60000)
if (api) await api.stop().catch(() => {})
if (mysql) await mysql.stop().catch(() => {})
if (oidc) await oidc.stop().catch(() => {})
if (api) addContext(this, {title: 'api-log', value: api.logRecords})
})

describe('GET /op/state', function () {
Expand Down Expand Up @@ -361,10 +366,11 @@ describe('Boot with STIGMAN_JWKS_CACHE_MAX_AGE out of range', function () {
})

after(async function () {
await mysql.stop()
await oidc.stop()
await api.stop()
addContext(this, {title: 'api-log', value: api.logRecords})
this.timeout(60000)
if (mysql) await mysql.stop().catch(() => {})
if (oidc) await oidc.stop().catch(() => {})
if (api) await api.stop().catch(() => {})
if (api) addContext(this, {title: 'api-log', value: api.logRecords})
})

describe('Mimimum value enforced', function () {
Expand All @@ -382,7 +388,8 @@ describe('Boot with STIGMAN_JWKS_CACHE_MAX_AGE out of range', function () {
})
})
after(async function () {
await api.stop()
this.timeout(60000)
if (api) await api.stop().catch(() => {})
})
it('should return minimum oauth.maxCacheAge (1)', async function () {
const configLog = api.logRecords.filter(r => r.type === 'configuration')[0]
Expand All @@ -405,7 +412,8 @@ describe('Boot with STIGMAN_JWKS_CACHE_MAX_AGE out of range', function () {
})
})
after(async function () {
await api.stop()
this.timeout(60000)
if (api) await api.stop().catch(() => {})
})
it('should return maximum oauth.maxCacheAge (35791)', async function () {
const configLog = api.logRecords.filter(r => r.type === 'configuration')[0]
Expand All @@ -428,7 +436,8 @@ describe('Boot with STIGMAN_JWKS_CACHE_MAX_AGE out of range', function () {
})
})
after(async function () {
await api.stop()
this.timeout(60000)
if (api) await api.stop().catch(() => {})
})
it('should return default oauth.maxCacheAge (10)', async function () {
const configLog = api.logRecords.filter(r => r.type === 'configuration')[0]
Expand Down
63 changes: 27 additions & 36 deletions test/state/mocha/db.test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { expect } from 'chai'
import { getPorts, spawnApiPromise, spawnMySQL, simpleRequest, execIpTables } from './lib.js'
import { getPorts, spawnApiPromise, spawnMySQL, simpleRequest, execIpTables, waitForLog } from './lib.js'
import MockOidc from '../../utils/mockOidc.js'
import addContext from 'mochawesome/addContext.js'

Expand All @@ -10,16 +10,7 @@ describe('DB outage: shutdown', function () {
let mysql
let oidc

async function waitLogEvent(type, count = 1) {
let seen = 0
return new Promise((resolve) => {
api.logEvents.on(type, function (log) {
seen++
if (seen >= count) resolve(log)
})
})
}


before(async function () {
this.timeout(60000)
oidc = new MockOidc({keyCount: 1, includeInsecureKid: false})
Expand Down Expand Up @@ -47,10 +38,11 @@ describe('DB outage: shutdown', function () {
})

after(async function () {
await api.stop()
await mysql.stop()
await oidc.stop()
addContext(this, {title: 'api-log', value: api.logRecords})
this.timeout(60000)
if (api) await api.stop().catch(() => {})
if (mysql) await mysql.stop().catch(() => {})
if (oidc) await oidc.stop().catch(() => {})
if (api) addContext(this, {title: 'api-log', value: api.logRecords})
})

describe('DB up', function () {
Expand All @@ -64,8 +56,10 @@ describe('DB outage: shutdown', function () {
})

describe('DB shutdown', function () {
let logMark
before(async function () {
this.timeout(30000)
logMark = api.logRecords.length
await mysql.stop()
console.log(' mysql shutdown')
})
Expand All @@ -74,29 +68,31 @@ describe('DB outage: shutdown', function () {
const res = await simpleRequest(`${apiOrigin}/api/op/state`)
expect(res.status).to.equal(200)
expect(res.body.currentState).to.equal('unavailable')
expect(res.body.dependencies).to.eql({db: false, oidc: true})
expect(res.body.dependencies).to.eql({db: false, oidc: true})
})

it('should log retry fail', async function () {
this.timeout(30000)
console.log(' wait for log: restore (2)')
const log = await waitLogEvent('restore', 2)
const log = await waitForLog(api, 'restore', {count: 2, since: logMark})
expect(log.data.message).to.equal(`connect ECONNREFUSED 127.0.0.1:${dbPort}`)
})
})

describe('DB restarted', function() {
let logMark
before( async function() {
this.timeout(30000)
console.log(' try mysql restart')
logMark = api.logRecords.length
mysql = await spawnMySQL({tag: '8.0.24', port: dbPort})
console.log(' ✔ mysql restarted')
})

it('should return state "available"', async function () {
this.timeout(60000)
console.log(' wait for log: state-changed')
const log = await waitLogEvent('state-changed')
const log = await waitForLog(api, 'state-changed', {since: logMark})
expect(log.data.currentState).to.equal('available')
expect(log.data.previousState).to.equal('unavailable')
const res = await simpleRequest(`${apiOrigin}/api/op/state`)
Expand All @@ -112,16 +108,6 @@ describe('DB outage: network/host down', function () {
let mysql
let oidc

async function waitLogEvent(type, count = 1) {
let seen = 0
return new Promise((resolve) => {
api.logEvents.on(type, function (log) {
seen++
if (seen >= count) resolve(log)
})
})
}

before(async function () {
this.timeout(60000)
oidc = new MockOidc({keyCount: 1, includeInsecureKid: false})
Expand Down Expand Up @@ -149,10 +135,11 @@ describe('DB outage: network/host down', function () {
})

after(async function () {
await api.stop()
await mysql.stop()
await oidc.stop()
addContext(this, {title: 'api-log', value: api.logRecords})
this.timeout(60000)
if (api) await api.stop().catch(() => {})
if (mysql) await mysql.stop().catch(() => {})
if (oidc) await oidc.stop().catch(() => {})
if (api) addContext(this, {title: 'api-log', value: api.logRecords})
})

describe('Network/host up', function () {
Expand All @@ -166,41 +153,45 @@ describe('DB outage: network/host down', function () {
})

describe('Network/host down', function () {
let logMark
before(async function () {
logMark = api.logRecords.length
execIpTables(`-A OUTPUT -p tcp --dport ${dbPort} -j DROP`)
console.log(' iptables dropping packets')
})
it('should return state "unavailable"', async function () {
this.timeout(30000)
console.log(' wait for log: state-changed')
const log = await waitLogEvent('state-changed')
const log = await waitForLog(api, 'state-changed', {since: logMark})
expect(log.data.currentState).to.equal('unavailable')
expect(log.data.previousState).to.equal('available')
const res = await simpleRequest(`${apiOrigin}/api/op/state`)
expect(res.status).to.equal(200)
expect(res.body.currentState).to.equal('unavailable')
expect(res.body.dependencies).to.eql({db: false, oidc: true})
expect(res.body.dependencies).to.eql({db: false, oidc: true})
})

it('should log retry fail', async function () {
this.timeout(45000)
console.log(' wait for log: restore (2)')
const log = await waitLogEvent('restore', 2)
const log = await waitForLog(api, 'restore', {count: 2, since: logMark})
expect(log.data.message).to.equal('connect ETIMEDOUT')
})
})

describe('Network/host up', function() {
let logMark
before( async function() {
this.timeout(30000)
logMark = api.logRecords.length
execIpTables(`-D OUTPUT -p tcp --dport ${dbPort} -j DROP`)
console.log(' iptables accepting packets')
})

it('should return state "available"', async function () {
this.timeout(60000)
console.log(' wait for log: state-changed')
const log = await waitLogEvent('state-changed')
const log = await waitForLog(api, 'state-changed', {since: logMark})
expect(log.data.currentState).to.equal('available')
expect(log.data.previousState).to.equal('unavailable')
const res = await simpleRequest(`${apiOrigin}/api/op/state`)
Expand Down
11 changes: 6 additions & 5 deletions test/state/mocha/jwks.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ describe('JWKS Tests', function () {
const {apiPort, dbPort, oidcPort, apiOrigin, oidcOrigin} = getPorts(54020)

before(async function () {
this.timeout(30000)
this.timeout(60000)
oidc = new MockOidc({keyCount: 1, includeInsecureKid: false})
tokens.rotation0 = oidc.getToken({username: 'prerotation', privileges:['create_collection']}) // default privileges
oidc.rotateKeys({keyCount: 1, includeInsecureKid: false})
Expand Down Expand Up @@ -46,10 +46,11 @@ describe('JWKS Tests', function () {
})

after(async function () {
await api.stop()
await mysql.stop()
await oidc.stop()
addContext(this, {title: 'api-log', value: api.logRecords})
this.timeout(60000)
if (api) await api.stop().catch(() => {})
if (mysql) await mysql.stop().catch(() => {})
if (oidc) await oidc.stop().catch(() => {})
if (api) addContext(this, {title: 'api-log', value: api.logRecords})
})

describe('Create user according to token', function () {
Expand Down
Loading
Loading