Add complete system qualification evidence

This commit is contained in:
ms2k 2026-07-18 14:09:04 +09:00
commit 6952767503

View file

@ -0,0 +1,158 @@
import { createPrivateKey, sign } from 'node:crypto'
import { expect, test } from '@playwright/test'
test('authenticates and completes a deployed job lifecycle through ingress', async ({
page,
}) => {
const token = await mintZitadelToken()
const claims = jwtClaims(token)
const issuer = requiredEnv('REMORA_OIDC_ISSUER')
await page.goto('/')
await page.evaluate(
({ accessToken, authority, profile }) => {
sessionStorage.setItem(
`oidc.user:${authority}:remora-web`,
JSON.stringify({
access_token: accessToken,
token_type: 'Bearer',
scope: 'openid profile',
profile,
expires_at: Number(profile.exp),
}),
)
},
{ accessToken: token, authority: issuer, profile: claims },
)
await page.reload()
await expect(page.getByRole('heading', { name: 'Remora' })).toBeVisible()
await expect(page.getByRole('button', { name: 'Sign out' })).toBeVisible()
await expect(page.getByLabel('Synchronization status')).toHaveText('live')
await expect(page.getByText('deployment-coordination-rebuild')).toBeVisible()
await page.getByRole('button', { name: 'Submit job' }).click()
await page.getByLabel('Label').fill('Browser system qualification')
await page.getByLabel('URL').fill('https://example.com/system.bin')
await page.getByLabel('Result path').fill('system.bin')
const submitted = page.waitForResponse(
(response) =>
response.request().method() === 'POST' &&
new URL(response.url()).pathname === '/v1/jobs',
)
await page.getByRole('button', { name: 'Submit job now' }).click()
const response = await submitted
if (!response.ok())
throw new Error(
`job submission failed: ${response.status()} ${await response.text()}`,
)
await expect(page.getByText('Job accepted')).toBeVisible()
await expect(
page.getByRole('heading', { name: 'Browser system qualification' }),
).toBeVisible()
await page.getByRole('button', { name: 'Cancel job' }).click()
await page.getByRole('button', { name: 'Confirm' }).click()
await expect(page.getByText('Cancellation requested')).toBeVisible()
})
async function mintZitadelToken(): Promise<string> {
const issuer = requiredEnv('REMORA_OIDC_ISSUER').replace(/\/$/, '')
const tokenUrl = `${issuer}/oauth/v2/token`
const key = jsonObject(
Buffer.from(
requiredEnv('REMORA_OIDC_USER_KEY_B64'),
'base64url',
).toString(),
)
const subject = stringValue(key.userId ?? key.userID, 'user id')
const keyId = stringValue(key.keyId ?? key.key_id, 'key id')
const privateKey = stringValue(key.key, 'private key').replaceAll(
String.raw`\n`,
'\n',
)
const projectId = requiredEnv('REMORA_OIDC_PROJECT_ID')
const scope = [
'openid',
'profile',
`urn:zitadel:iam:org:project:id:${projectId}:aud`,
'urn:zitadel:iam:org:project:roles',
'urn:zitadel:iam:org:projects:roles',
`urn:zitadel:iam:org:project:${projectId}:roles`,
].join(' ')
let failure = ''
for (const audience of [tokenUrl, issuer]) {
const response = await fetch(tokenUrl, {
method: 'POST',
headers: { 'content-type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
assertion: assertion(subject, keyId, audience, privateKey),
scope,
}),
})
if (response.ok) {
const body = jsonObject(await response.text())
return stringValue(body.access_token, 'access token')
}
failure = `${response.status} ${await response.text()}`
}
throw new Error(`token endpoint rejected assertion: ${failure}`)
}
function assertion(
subject: string,
keyId: string,
audience: string,
privateKey: string,
): string {
const now = Math.floor(Date.now() / 1000)
const header = encode({ alg: 'RS256', kid: keyId, typ: 'JWT' })
const payload = encode({
iss: subject,
sub: subject,
aud: audience,
iat: now,
exp: now + 3600,
})
const unsigned = `${header}.${payload}`
const signature = sign(
'RSA-SHA256',
Buffer.from(unsigned),
createPrivateKey(privateKey),
).toString('base64url')
return `${unsigned}.${signature}`
}
function jwtClaims(token: string): Record<string, unknown> {
const payload = token.split('.')[1]
if (payload === undefined) throw new Error('access token is not a JWT')
return jsonObject(Buffer.from(payload, 'base64url').toString())
}
function encode(value: unknown): string {
return Buffer.from(JSON.stringify(value)).toString('base64url')
}
function stringValue(value: unknown, name: string): string {
if (typeof value !== 'string' || value === '')
throw new Error(`missing ${name}`)
return value
}
function jsonObject(source: string): Record<string, unknown> {
const value: unknown = JSON.parse(source)
if (!isJsonObject(value)) throw new Error('expected a JSON object')
return value
}
function isJsonObject(value: unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null && !Array.isArray(value)
}
function requiredEnv(name: string): string {
const value = process.env[name]
if (value === undefined || value === '')
throw new Error(`${name} is required`)
return value
}