WeKnora deployment source with the reviewed wiki identity, summary and recovery fixes
Anydoc / anydoc parser engine (push) Canceled after 0s
App / Format, vet, test, and build (push) Canceled after 0s
CLI / test (macos-latest / go 1.26) (push) Canceled after 0s
CLI / test (ubuntu-latest / go 1.26) (push) Canceled after 0s
CLI / test (windows-latest / go 1.26) (push) Canceled after 0s
Build and Push Docker Image / build-ui (push) Canceled after 0s
Build and Push Docker Image / build-docreader (push) Canceled after 0s
Build and Push Docker Image / build-sandbox (push) Canceled after 0s
Build and Push Docker Image / build-app (amd64, linux/amd64, ubuntu-latest) (push) Canceled after 0s
Build and Push Docker Image / build-app (arm64, linux/arm64, ubuntu-24.04-arm) (push) Canceled after 0s
DocReader / Python tests and Go client tests (push) Canceled after 0s
Build & Publish (dsh-weknora) / Typecheck, test, pack (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (pinned) (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (latest, advisory) (push) Canceled after 0s
Frontend / Test, type-check, and build (push) Canceled after 0s
Go Lint Cache / warm lint cache (.) (push) Canceled after 0s
Go Lint Cache / warm lint cache (cli) (push) Canceled after 0s
Go Lint Cache / warm lint cache (client) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.10) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.11) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.12) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.13) (push) Canceled after 0s
Build and Push Docker Image / merge (push) Canceled after 0s
Build & Publish (dsh-weknora) / Publish to npm (push) Canceled after 0s
Build & Publish (mcp-server) / Build sdist + wheel (push) Canceled after 0s
Build & Publish (mcp-server) / Publish to PyPI (push) Canceled after 0s
Anydoc / anydoc parser engine (push) Canceled after 0s
App / Format, vet, test, and build (push) Canceled after 0s
CLI / test (macos-latest / go 1.26) (push) Canceled after 0s
CLI / test (ubuntu-latest / go 1.26) (push) Canceled after 0s
CLI / test (windows-latest / go 1.26) (push) Canceled after 0s
Build and Push Docker Image / build-ui (push) Canceled after 0s
Build and Push Docker Image / build-docreader (push) Canceled after 0s
Build and Push Docker Image / build-sandbox (push) Canceled after 0s
Build and Push Docker Image / build-app (amd64, linux/amd64, ubuntu-latest) (push) Canceled after 0s
Build and Push Docker Image / build-app (arm64, linux/arm64, ubuntu-24.04-arm) (push) Canceled after 0s
DocReader / Python tests and Go client tests (push) Canceled after 0s
Build & Publish (dsh-weknora) / Typecheck, test, pack (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (pinned) (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (latest, advisory) (push) Canceled after 0s
Frontend / Test, type-check, and build (push) Canceled after 0s
Go Lint Cache / warm lint cache (.) (push) Canceled after 0s
Go Lint Cache / warm lint cache (cli) (push) Canceled after 0s
Go Lint Cache / warm lint cache (client) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.10) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.11) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.12) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.13) (push) Canceled after 0s
Build and Push Docker Image / merge (push) Canceled after 0s
Build & Publish (dsh-weknora) / Publish to npm (push) Canceled after 0s
Build & Publish (mcp-server) / Build sdist + wheel (push) Canceled after 0s
Build & Publish (mcp-server) / Publish to PyPI (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,146 @@
|
||||
const assert = require('node:assert/strict')
|
||||
const { execFileSync } = require('node:child_process')
|
||||
const fs = require('node:fs')
|
||||
const os = require('node:os')
|
||||
const path = require('node:path')
|
||||
const test = require('node:test')
|
||||
const { setTimeout: delay } = require('node:timers/promises')
|
||||
|
||||
const frontend = path.resolve(__dirname, '../frontend')
|
||||
const source = fs.readFileSync(path.join(frontend, 'nginx.conf'), 'utf8')
|
||||
|
||||
test('ingress access format exposes only approved fields and preserves nginx variables', () => {
|
||||
const format = source.match(/log_format ingress_timing escape=json\s+([\s\S]*?);/)
|
||||
assert.ok(format, 'escaped timing format exists')
|
||||
const variables = [...new Set(format[1].match(/\$[a-z][a-z0-9_]*/g))].sort()
|
||||
assert.deepEqual(variables, [
|
||||
'$ingress_cf_ray', '$ingress_route_class', '$ingress_upstream_connect_time',
|
||||
'$ingress_upstream_header_time', '$ingress_upstream_response_time', '$request_time', '$status',
|
||||
'$time_iso8601',
|
||||
].sort())
|
||||
const active = source.split('\n').filter(line => !line.trim().startsWith('#')).join('\n')
|
||||
assert.deepEqual([...active.matchAll(/access_log\s+([^;]+);/g)].map(match => match[1]), [
|
||||
'/var/log/nginx/access.log ingress_timing',
|
||||
])
|
||||
const entrypoint = fs.readFileSync(path.join(frontend, 'docker-entrypoint.sh'), 'utf8')
|
||||
const substituted = entrypoint.match(/envsubst '([^']+)'/)
|
||||
assert.ok(substituted)
|
||||
for (const variable of variables) assert.ok(!substituted[1].includes(variable))
|
||||
})
|
||||
|
||||
const dockerContext = process.env.NGINX_TEST_DOCKER_CONTEXT
|
||||
const docker = (...args) => execFileSync('docker', [
|
||||
...(dockerContext ? ['--context', dockerContext] : []), ...args,
|
||||
], { encoding: 'utf8', timeout: 30000, stdio: ['ignore', 'pipe', 'pipe'] }).trim()
|
||||
|
||||
// Enable only after the coordinated runtime freeze. No production network is used.
|
||||
test('isolated nginx classifies requests and records timings without request secrets', {
|
||||
skip: process.env.NGINX_TEST_RUN_INGRESS !== '1', timeout: 90000,
|
||||
}, async () => {
|
||||
const temp = fs.mkdtempSync(path.join(os.tmpdir(), 'weknora-ingress-test-'))
|
||||
const container = `weknora-ingress-test-${process.pid}-${Date.now()}`
|
||||
const seedContainer = `${container}-seed`
|
||||
const volume = `${container}-fixtures`
|
||||
const image = process.env.NGINX_TEST_IMAGE || fs.readFileSync(path.join(frontend, 'Dockerfile'), 'utf8').match(/^FROM (\S+)/m)[1]
|
||||
let created = false
|
||||
let seedCreated = false
|
||||
let volumeCreated = false
|
||||
try {
|
||||
const site = path.join(temp, 'html')
|
||||
fs.mkdirSync(site)
|
||||
for (const [filename, body] of Object.entries({
|
||||
'index.html': '<html>timing fixture</html>',
|
||||
'embed.html': '<html>embed fixture</html>',
|
||||
'config.js': 'window.fixture = true;',
|
||||
'release.json': '{"release":"fixture"}',
|
||||
})) fs.writeFileSync(path.join(site, filename), body)
|
||||
|
||||
const replacements = { APP_SCHEME: 'http', APP_HOST: '127.0.0.1', APP_PORT: '81', MAX_FILE_SIZE: '50M', MAX_SKILL_BUNDLE_SIZE: '256M' }
|
||||
const config = source.replace(/\$\{(APP_SCHEME|APP_HOST|APP_PORT|MAX_FILE_SIZE|MAX_SKILL_BUNDLE_SIZE)\}/g, (_, key) => replacements[key])
|
||||
.replaceAll('/usr/share/nginx/html', '/fixtures/html')
|
||||
.replaceAll('/etc/nginx/api-proxy.conf', '/fixtures/api-proxy.conf')
|
||||
.replaceAll('/var/log/nginx/access.log ingress_timing', '/proc/1/fd/1 ingress_timing')
|
||||
.replaceAll('/var/log/nginx/error.log warn', '/proc/1/fd/2 warn')
|
||||
fs.writeFileSync(path.join(temp, 'nginx.conf'),
|
||||
'worker_processes 1; error_log /dev/stderr warn; pid /var/run/nginx.pid;\n'
|
||||
+ 'events { worker_connections 128; }\nhttp { include /etc/nginx/mime.types;\n'
|
||||
+ config + '\nserver { listen 127.0.0.1:81; access_log off; location / { return 204; } }\n}\n')
|
||||
fs.copyFileSync(path.join(frontend, 'nginx-api-proxy.conf'), path.join(temp, 'api-proxy.conf'))
|
||||
// Stage through Docker copy so a remote context needs no host-path sharing.
|
||||
docker('volume', 'create', volume)
|
||||
volumeCreated = true
|
||||
docker('create', '--pull', 'never', '--read-only', '--network', 'none', '--name', seedContainer,
|
||||
'--mount', `type=volume,src=${volume},dst=/fixtures`, '--entrypoint', 'true', image)
|
||||
seedCreated = true
|
||||
docker('cp', `${temp}/.`, `${seedContainer}:/fixtures`)
|
||||
docker('rm', seedContainer)
|
||||
seedCreated = false
|
||||
docker('create', '--rm', '--pull', 'never', '--read-only', '--network', 'none', '--name', container,
|
||||
'--tmpfs', '/var/cache/nginx:rw,noexec,nosuid,size=16m',
|
||||
'--tmpfs', '/var/run:rw,noexec,nosuid,size=1m',
|
||||
'--mount', `type=volume,src=${volume},dst=/fixtures,readonly`,
|
||||
'--entrypoint', 'nginx', image, '-c', '/fixtures/nginx.conf', '-g', 'daemon off;')
|
||||
created = true
|
||||
docker('start', container)
|
||||
docker('exec', container, 'nginx', '-t', '-c', '/fixtures/nginx.conf')
|
||||
const fetchFixture = (url, extra = []) => docker('exec', container, 'wget', '-q', '-O', '/dev/null', ...extra, `http://127.0.0.1${url}`)
|
||||
for (let attempt = 0; ; attempt += 1) {
|
||||
try { fetchFixture('/release.json'); break } catch (error) {
|
||||
if (attempt >= 19) throw error
|
||||
await delay(250)
|
||||
}
|
||||
}
|
||||
|
||||
const secret = 'ingress-secret-sentinel'
|
||||
const cases = [
|
||||
['/admin', 'shell', 200], ['/platform/workbench/content', 'shell', 200],
|
||||
['/api/v1/auth/me', 'auth_me', 204], ['/api/v1/access/me', 'access_me', 204],
|
||||
['/api/v1/system/capabilities', 'capabilities', 204],
|
||||
['/api/v1/workbench/resources/training-maps', 'resource_list', 204],
|
||||
[`/api/v1/workbench/resources/training-maps/${secret}`, 'resource_detail', 204],
|
||||
[`/api/v1/workbench/resources/training-maps/${secret}/jobs`, 'api', 204],
|
||||
[`/r/${secret}`, 'resource_link', 204], [`/files/${secret}`, 'file', 200],
|
||||
[`/embed/${secret}`, 'embed', 200], ['/config.js', 'config', 200],
|
||||
['/release.json', 'release', 200], [`/unknown/${secret}`, 'other', 200],
|
||||
]
|
||||
const rays = cases.map((_, index) => `${(index + 1).toString(16).padStart(16, '0')}-AMS`)
|
||||
for (const [index, [url]] of cases.entries()) fetchFixture(`${url}?token=${secret}`, [
|
||||
`--header=CF-Ray: ${rays[index]}`, `--header=Authorization: Bearer ${secret}`,
|
||||
`--header=Cookie: session=${secret}`, `--header=Referer: https://example.test/${secret}`,
|
||||
`--header=User-Agent: ${secret}`,
|
||||
])
|
||||
fetchFixture('/api/body', [`--header=CF-Ray: ${secret}`, `--post-data=${secret}`])
|
||||
const lines = docker('logs', container).split('\n').filter(line => line.startsWith('{"time":'))
|
||||
assert.ok(lines.length >= cases.length + 2)
|
||||
assert.ok(!lines.join('\n').includes(secret), 'no path, query, header, or body secret appears in access records')
|
||||
const records = lines.map(line => JSON.parse(line))
|
||||
const fields = ['time', 'route', 'status', 'request_time', 'upstream_connect_time', 'upstream_header_time', 'upstream_response_time', 'cf_ray'].sort()
|
||||
for (const [index, [, route, status]] of cases.entries()) {
|
||||
const matches = records.filter(record => record.cf_ray === rays[index])
|
||||
assert.equal(matches.length, 1)
|
||||
const record = matches[0]
|
||||
assert.deepEqual(Object.keys(record).sort(), fields)
|
||||
assert.equal(record.route, route)
|
||||
assert.equal(record.status, status, `${route} ${record.cf_ray}`)
|
||||
assert.match(record.request_time, /^\d+\.\d{3}$/)
|
||||
for (const field of ['upstream_connect_time', 'upstream_header_time', 'upstream_response_time']) {
|
||||
assert.match(record[field], status === 204 ? /^\d+\.\d{3}$/ : /^-$/)
|
||||
}
|
||||
}
|
||||
assert.ok(records.some(record => record.route === 'api' && record.cf_ray === '-' && record.status === 204))
|
||||
} finally {
|
||||
try {
|
||||
if (created) docker('rm', '--force', container)
|
||||
} finally {
|
||||
try {
|
||||
if (seedCreated) docker('rm', '--force', seedContainer)
|
||||
} finally {
|
||||
try {
|
||||
if (volumeCreated) docker('volume', 'rm', volume)
|
||||
} finally {
|
||||
fs.rmSync(temp, { recursive: true, force: true })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user