Files
stack/packages/mosaic/src/credentials/delegated-credential.ts
T

95 lines
2.7 KiB
TypeScript

import { randomUUID } from 'node:crypto';
import { fstatSync } from 'node:fs';
import { readFile } from 'node:fs/promises';
import { z } from 'zod';
import type { ResolvedCredential } from './credential-provider.dto.js';
const authoritySchema = z
.object({
identity: z.string().regex(/^[A-Za-z0-9][A-Za-z0-9_.-]*$/),
estate: z.string().regex(/^[a-z0-9][a-z0-9-]*$/),
host: z.string().regex(/^[a-z0-9][a-z0-9.-]*$/),
secret: z
.string()
.min(1)
.max(16 * 1024)
.regex(/^\S+$/),
})
.strict();
export class DelegatedCredentialError extends Error {
constructor(
public readonly code: string,
message: string,
) {
super(`Delegated credential rejected: code=${code} ${message}`);
this.name = 'DelegatedCredentialError';
}
}
export async function readDelegatedCredentialFromFd(
fd: number,
expectedIdentity: string,
expectedEstate: string,
expectedHost: string,
): Promise<ResolvedCredential> {
if (!Number.isSafeInteger(fd) || fd < 3 || fd > 1024) {
throw new DelegatedCredentialError('delegated-authority-unavailable', 'invalid inherited fd');
}
let bytes: Buffer;
try {
const stat = fstatSync(fd);
if (!stat.isFile() && !stat.isFIFO()) {
throw new Error('fd is not a regular file or pipe');
}
bytes = await readFile(`/proc/self/fd/${fd}`);
} catch {
throw new DelegatedCredentialError(
'delegated-authority-unavailable',
'protected inherited credential fd could not be read',
);
}
if (bytes.byteLength > 32 * 1024) {
bytes.fill(0);
throw new DelegatedCredentialError(
'delegated-authority-unavailable',
'protected credential payload exceeded the bound',
);
}
let raw: unknown;
try {
raw = JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(bytes));
} catch {
bytes.fill(0);
throw new DelegatedCredentialError(
'delegated-authority-unavailable',
'protected credential payload was invalid',
);
}
bytes.fill(0);
const parsed = authoritySchema.safeParse(raw);
if (!parsed.success) {
throw new DelegatedCredentialError(
'delegated-authority-unavailable',
'protected credential payload did not match the schema',
);
}
if (
parsed.data.identity !== expectedIdentity ||
parsed.data.estate !== expectedEstate ||
parsed.data.host !== expectedHost
) {
throw new DelegatedCredentialError(
'delegated-authority-mismatch',
'protected credential does not match the explicit actor, estate, and host',
);
}
return Object.freeze({
identity: parsed.data.identity,
estate: parsed.data.estate,
host: parsed.data.host,
resolutionId: randomUUID(),
secret: new TextEncoder().encode(parsed.data.secret),
});
}