Skip to content

Commit a90e012

Browse files
authored
Add status endpoint (#515)
* Add status endpoint * Prettier
1 parent 59af12d commit a90e012

9 files changed

Lines changed: 327 additions & 27 deletions

File tree

src/config/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ export const BaseSettingsDefinition = {
9090
'The URL of the Redis server. Format: [redis[s]:]//[[user][:password@]][host][:port][/db-number][?db=db-number[&password=bar[&option=value]]]',
9191
type: 'string',
9292
validate: validator.url(),
93+
sensitive: true,
9394
},
9495
CACHE_TYPE: {
9596
description: 'The type of cache to use throughout the EA',
@@ -302,6 +303,7 @@ export const BaseSettingsDefinition = {
302303
description: 'Base64 Private Key of TSL/SSL certificate',
303304
type: 'string',
304305
validate: validator.base64(),
306+
sensitive: true,
305307
},
306308
TLS_PUBLIC_KEY: {
307309
description: 'Base64 Public Key of TSL/SSL certificate',
@@ -312,10 +314,12 @@ export const BaseSettingsDefinition = {
312314
description: 'Password to be used to generate an encryption key',
313315
type: 'string',
314316
default: '',
317+
sensitive: true,
315318
},
316319
TLS_CA: {
317320
description: 'CA certificate to use for authenticating client certificates',
318321
type: 'string',
322+
sensitive: true,
319323
},
320324
MAX_HTTP_REQUEST_QUEUE_LENGTH: {
321325
description:

src/debug/router.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { FastifyInstance } from 'fastify'
22
import { join } from 'path'
33
import { Adapter } from '../adapter'
4-
import settingsPage, { buildDebugSettingsList } from './settings-page'
4+
import settingsPage from './settings-page'
5+
import { buildSettingsList } from '../util/settings'
56

67
/**
78
* This function registers the debug endpoints for the adapter.
@@ -13,7 +14,7 @@ import settingsPage, { buildDebugSettingsList } from './settings-page'
1314
export default function registerDebugEndpoints(app: FastifyInstance, adapter: Adapter) {
1415
// Debug endpoint to return the current settings in raw JSON (censoring sensitive values)
1516
app.get(join(adapter.config.settings.BASE_URL, '/debug/settings/raw'), async () => {
16-
const censoredSettings = buildDebugSettingsList(adapter)
17+
const censoredSettings = buildSettingsList(adapter)
1718
return JSON.stringify(
1819
censoredSettings.sort((a, b) => a.name.localeCompare(b.name)),
1920
null,
@@ -23,7 +24,7 @@ export default function registerDebugEndpoints(app: FastifyInstance, adapter: Ad
2324

2425
// Helpful UI to visualize current settings
2526
app.get(join(adapter.config.settings.BASE_URL, '/debug/settings'), (req, reply) => {
26-
const censoredSettings = buildDebugSettingsList(adapter)
27+
const censoredSettings = buildSettingsList(adapter)
2728
const censoredSettingsPage = settingsPage(censoredSettings)
2829
reply.headers({ 'content-type': 'text/html' }).send(censoredSettingsPage)
2930
})

src/debug/settings-page.ts

Lines changed: 1 addition & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,4 @@
1-
import { Adapter } from '../adapter'
2-
import { SettingDefinitionDetails } from '../config'
3-
import { censor } from '../util'
4-
import CensorList from '../util/censor/censor-list'
5-
6-
export type DebugPageSetting = SettingDefinitionDetails & { name: string; value: unknown }
7-
8-
export const buildDebugSettingsList = (adapter: Adapter): DebugPageSetting[] => {
9-
// Censor EA settings
10-
const settings = adapter.config.settings
11-
const censoredValues = CensorList.getAll()
12-
const censoredSettings: Array<SettingDefinitionDetails & { name: string; value: unknown }> = []
13-
for (const [key, value] of Object.entries(settings)) {
14-
const definitionDetails = adapter.config.getSettingDebugDetails(key)
15-
censoredSettings.push({
16-
name: key,
17-
...definitionDetails,
18-
value: censor(value, censoredValues),
19-
})
20-
}
21-
return censoredSettings
22-
}
1+
import { DebugPageSetting } from '../util/settings'
232

243
// To enable syntax highlighting in VSCode, you can download the "Comment tagged templates" extension:
254
// https://marketplace.visualstudio.com/items?itemName=bierner.comment-tagged-templates

src/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
} from './util'
1717
import { errorCatchingMiddleware, validatorMiddleware } from './validation'
1818
import { EmptyInputParameters } from './validation/input-params'
19+
import registerStatusEndpoint from './status/router'
1920

2021
export { FastifyInstance as ServerInstance }
2122

@@ -210,6 +211,9 @@ async function buildRestApi(adapter: Adapter) {
210211
logger.info('Serving debug endpoints')
211212
}
212213

214+
// Register status endpoint
215+
registerStatusEndpoint(app, adapter)
216+
213217
// Use global error handling
214218
app.setErrorHandler(errorCatchingMiddleware)
215219

src/status/router.ts

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import { FastifyInstance } from 'fastify'
2+
import { join } from 'path'
3+
import { hostname } from 'os'
4+
import { Adapter } from '../adapter'
5+
import { buildSettingsList } from '../util/settings'
6+
7+
export interface StatusResponse {
8+
adapter: {
9+
name: string
10+
version: string
11+
uptimeSeconds: number
12+
}
13+
endpoints: {
14+
name: string
15+
aliases: string[]
16+
transports: string[]
17+
}[]
18+
defaultEndpoint: string
19+
configuration: {
20+
name: string
21+
value: unknown
22+
type: string
23+
description: string
24+
required: boolean
25+
default: unknown
26+
customSetting: boolean
27+
envDefaultOverride: unknown
28+
}[]
29+
runtime: {
30+
nodeVersion: string
31+
platform: string
32+
architecture: string
33+
hostname: string
34+
}
35+
metrics: {
36+
enabled: boolean
37+
port?: number
38+
endpoint?: string
39+
}
40+
}
41+
42+
/**
43+
* This function registers the status endpoint for the adapter.
44+
* This endpoint provides comprehensive information about the adapter including:
45+
* - Adapter metadata (name, version, commit)
46+
* - Configuration (obfuscated sensitive values)
47+
* - Runtime information
48+
* - Dependencies status
49+
* - Endpoints and transports
50+
*
51+
* @param app - the fastify instance that has been created
52+
* @param adapter - the adapter for which to create the status endpoint
53+
*/
54+
export default function registerStatusEndpoint(app: FastifyInstance, adapter: Adapter) {
55+
// Status endpoint that returns comprehensive adapter information
56+
app.get(join(adapter.config.settings.BASE_URL, '/status'), async () => {
57+
const metricsEndpoint = adapter.config.settings.METRICS_USE_BASE_URL
58+
? join(adapter.config.settings.BASE_URL, 'metrics')
59+
: '/metrics'
60+
61+
const statusResponse: StatusResponse = {
62+
adapter: {
63+
name: adapter.name,
64+
version: process.env['npm_package_version'] || 'unknown',
65+
uptimeSeconds: process.uptime(),
66+
},
67+
endpoints: adapter.endpoints.map((endpoint) => ({
68+
name: endpoint.name,
69+
aliases: endpoint.aliases || [],
70+
transports: endpoint.transportRoutes.routeNames(),
71+
})),
72+
defaultEndpoint: adapter.defaultEndpoint || '',
73+
configuration: buildSettingsList(adapter),
74+
runtime: {
75+
nodeVersion: process.version,
76+
platform: process.platform,
77+
architecture: process.arch,
78+
hostname: hostname(),
79+
},
80+
metrics: {
81+
enabled: adapter.config.settings.METRICS_ENABLED,
82+
port: adapter.config.settings.METRICS_ENABLED
83+
? adapter.config.settings.METRICS_PORT
84+
: undefined,
85+
endpoint: adapter.config.settings.METRICS_ENABLED ? metricsEndpoint : undefined,
86+
},
87+
}
88+
89+
return statusResponse
90+
})
91+
}

src/util/settings.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { Adapter } from '../adapter'
2+
import { SettingDefinitionDetails } from '../config'
3+
import { censor } from './index'
4+
import CensorList from './censor/censor-list'
5+
6+
export type DebugPageSetting = SettingDefinitionDetails & { name: string; value: unknown }
7+
8+
/**
9+
* Builds a list of adapter settings with sensitive values censored
10+
* Used by both debug settings page and status endpoint
11+
*/
12+
export const buildSettingsList = (adapter: Adapter): DebugPageSetting[] => {
13+
// Censor EA settings
14+
const settings = adapter.config.settings
15+
const censoredValues = CensorList.getAll()
16+
const censoredSettings: Array<SettingDefinitionDetails & { name: string; value: unknown }> = []
17+
18+
for (const [key, value] of Object.entries(settings)) {
19+
const definitionDetails = adapter.config.getSettingDebugDetails(key)
20+
censoredSettings.push({
21+
name: key,
22+
...definitionDetails,
23+
value: censor(value, censoredValues),
24+
})
25+
}
26+
27+
return censoredSettings.sort((a, b) => a.name.localeCompare(b.name))
28+
}

test/config.test.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
import test from 'ava'
2-
import { AdapterConfig, getEnv, SettingDefinition, SettingsDefinitionMap } from '../src/config'
2+
import {
3+
AdapterConfig,
4+
BaseSettingsDefinition,
5+
getEnv,
6+
SettingDefinition,
7+
SettingsDefinitionMap,
8+
} from '../src/config'
39
import { validator } from '../src/validation/utils'
410

511
test.afterEach(async () => {
@@ -161,3 +167,23 @@ test.serial('Test validate function (scientific notation)', async (t) => {
161167
t.fail()
162168
}
163169
})
170+
171+
test('sensitive configuration constants are properly flagged', (t) => {
172+
// Extract all settings that are marked as sensitive
173+
const actualSensitiveSettings = Object.entries(BaseSettingsDefinition)
174+
.filter(([_, setting]) => (setting as { sensitive?: boolean }).sensitive === true)
175+
.map(([name]) => name)
176+
.sort()
177+
178+
// Expected list of settings that should be marked as sensitive
179+
const expectedSensitiveSettings = [
180+
'CACHE_REDIS_PASSWORD',
181+
'CACHE_REDIS_URL',
182+
'TLS_CA',
183+
'TLS_PASSPHRASE',
184+
'TLS_PRIVATE_KEY',
185+
].sort()
186+
187+
// Deep equal comparison
188+
t.deepEqual(actualSensitiveSettings, expectedSensitiveSettings)
189+
})

test/debug-endpoints.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import test from 'ava'
22
import { start } from '../src'
33
import { Adapter } from '../src/adapter'
44
import { AdapterConfig } from '../src/config'
5-
import { DebugPageSetting } from '../src/debug/settings-page'
5+
import { DebugPageSetting } from '../src/util/settings'
66

77
test.serial('debug endpoints return 404 if env var is omitted', async (t) => {
88
process.env['DEBUG_ENDPOINTS'] = undefined

0 commit comments

Comments
 (0)