初始提交:DSH 多租户平台(dshs)
This commit is contained in:
commit
43976fea6a
167 files changed
+24456
No files matched your search
@@ -0,0 +1,105 @@
|
||||
// Account-level isolation spawn flow: in 'account' mode the orchestrator passes
|
||||
// a deterministic uid to the setuid wrapper, and the child still runs through it.
|
||||
import { mkdirSync, mkdtempSync, readFileSync, rmSync } from 'node:fs'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { dirname, join } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { buildServer } from '../lib/web/server.js'
|
||||
import { resolveConfig } from '../lib/config.js'
|
||||
import { hashPassword } from '../lib/web/auth.js'
|
||||
|
||||
function assert(condition, message) {
|
||||
if (!condition) throw new Error('ASSERT: ' + message)
|
||||
}
|
||||
|
||||
const here = dirname(fileURLToPath(import.meta.url))
|
||||
const fakeDsh = join(here, 'fake-dsh.mjs')
|
||||
const fakeSetpriv = join(here, 'fake-setpriv.mjs')
|
||||
const dataRoot = mkdtempSync(join(tmpdir(), 'dsh-smoke-iso-'))
|
||||
|
||||
const app = await buildServer(
|
||||
resolveConfig({
|
||||
port: 0,
|
||||
dbPath: ':memory:',
|
||||
dataRoot,
|
||||
isolationMode: 'account',
|
||||
baseUid: 50000,
|
||||
spawnAsUserCommand: [process.execPath, fakeSetpriv, '--uid', '{UID}'],
|
||||
dshCommand: [process.execPath, fakeDsh],
|
||||
}),
|
||||
)
|
||||
await app.listen({ port: 0 })
|
||||
const base = `http://127.0.0.1:${app.server.address().port}`
|
||||
|
||||
await app.db.createUser({
|
||||
id: 'u1',
|
||||
username: 'gina',
|
||||
passHash: await hashPassword('ginapass123'),
|
||||
role: 'active',
|
||||
homeDir: '/tmp/u1-home',
|
||||
})
|
||||
mkdirSync(join(dataRoot, 'users', 'u1', 'ws', 'proj'), { recursive: true })
|
||||
|
||||
async function json(path, { method = 'GET', body, cookie } = {}) {
|
||||
const res = await fetch(base + path, {
|
||||
method,
|
||||
headers: {
|
||||
...(body ? { 'content-type': 'application/json' } : {}),
|
||||
...(cookie ? { cookie } : {}),
|
||||
},
|
||||
body: body ? JSON.stringify(body) : undefined,
|
||||
})
|
||||
const text = await res.text()
|
||||
return { status: res.status, body: text ? JSON.parse(text) : null, setCookie: res.headers.get('set-cookie') }
|
||||
}
|
||||
|
||||
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
|
||||
|
||||
try {
|
||||
let r = await json('/api/auth/login', { method: 'POST', body: { username: 'gina', password: 'ginapass123' } })
|
||||
const cookie = r.setCookie.split(';')[0]
|
||||
|
||||
r = await json('/api/dsh/launch', { method: 'POST', cookie, body: { folder: 'proj' } })
|
||||
console.log('launch ->', r.status)
|
||||
assert(r.status === 200, 'launch succeeds')
|
||||
|
||||
let uid
|
||||
for (let i = 0; i < 20; i++) {
|
||||
try {
|
||||
uid = readFileSync(join(dataRoot, 'users', 'u1', 'ws', 'proj', 'setpriv-uid.txt'), 'utf8').trim()
|
||||
break
|
||||
} catch {
|
||||
// the wrapper hasn't written yet
|
||||
}
|
||||
await sleep(100)
|
||||
}
|
||||
const expected = (await app.db.findUserById('u1')).uid
|
||||
console.log('setuid ->', uid, '(expected', expected + ')')
|
||||
assert(uid === String(expected), 'setuid wrapper received the DB-assigned uid')
|
||||
|
||||
let proxyText
|
||||
for (let i = 0; i < 20; i++) {
|
||||
try {
|
||||
const res = await fetch(base + '/u/u1/dsh/hello', { headers: { cookie } })
|
||||
if (res.status === 200) {
|
||||
proxyText = await res.text()
|
||||
break
|
||||
}
|
||||
} catch {
|
||||
// retry until the child is listening
|
||||
}
|
||||
await sleep(100)
|
||||
}
|
||||
console.log('proxy ->', proxyText !== undefined)
|
||||
assert(proxyText !== undefined && proxyText.includes('fake-dsh'), 'dsh runs through the setuid wrapper')
|
||||
|
||||
console.log('OK: account-level isolation spawn flow passed')
|
||||
} finally {
|
||||
await app.close()
|
||||
await sleep(300)
|
||||
try {
|
||||
rmSync(dataRoot, { recursive: true, force: true })
|
||||
} catch {
|
||||
// best-effort cleanup
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user