Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 3 additions & 27 deletions src/cloud-hypervisor/vmm-identity.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { randomBytes } from 'crypto';
import { promises as fs } from 'fs';
import * as path from 'path';
import execa from 'execa';
import { createDefaultIdentityDependencies } from '../identity-dependencies';

const ACCOUNT_PREFIX = 'awfvmm-';
const ACCOUNT_LOCK_DIRECTORY = '/run/awf-cloud-hypervisor/.account-lock';
Expand Down Expand Up @@ -54,32 +54,8 @@ export interface CloudHypervisorVmmIdentityDependencies {
processStartTime(pid: number): Promise<string | undefined>;
}

const defaultDependencies: CloudHypervisorVmmIdentityDependencies = {
mkdir: fs.mkdir,
writeFile: fs.writeFile,
readFile: fs.readFile,
rm: fs.rm,
rmdir: fs.rmdir,
lstat: fs.lstat,
run: async (command, args) => {
const result = await execa(command, [...args], {
reject: false,
stdio: ['ignore', 'pipe', 'pipe'],
env: { PATH: '/usr/sbin:/usr/bin:/sbin:/bin' },
extendEnv: false,
});
if (result.exitCode !== 0) {
throw new Error(
`${command} ${args.join(' ')} exited with code ${result.exitCode}: ` +
`${result.stderr.trim() || result.stdout.trim()}`,
);
}
return { stdout: result.stdout, stderr: result.stderr };
},
sleep: (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)),
pid: process.pid,
processStartTime: readProcessStartTime,
};
const defaultDependencies: CloudHypervisorVmmIdentityDependencies =
createDefaultIdentityDependencies(readProcessStartTime);

interface LockOwner {
readonly pid: number;
Expand Down
28 changes: 28 additions & 0 deletions src/identity-dependencies.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { promises as fs } from 'fs';
import { createDefaultIdentityDependencies } from './identity-dependencies';

describe('createDefaultIdentityDependencies', () => {
it('creates the shared filesystem and process dependency scaffold', async () => {
const processStartTime = jest.fn().mockResolvedValue('123');
const dependencies = createDefaultIdentityDependencies(processStartTime);

expect(dependencies).toMatchObject({
mkdir: fs.mkdir,
writeFile: fs.writeFile,
readFile: fs.readFile,
rm: fs.rm,
rmdir: fs.rmdir,
lstat: fs.lstat,
pid: process.pid,
processStartTime,
});
const result = await dependencies.run(process.execPath, [
'-e',
'process.stdout.write(process.env.PATH ?? "")',
]);
expect(result.stdout).toBe('/usr/sbin:/usr/bin:/sbin:/bin');
await expect(dependencies.run(process.execPath, ['-e', 'process.exit(1)']))
.rejects.toThrow(/exited with code 1/);
await expect(dependencies.sleep(0)).resolves.toBeUndefined();
});
});
55 changes: 55 additions & 0 deletions src/identity-dependencies.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { promises as fs } from 'fs';
import execa from 'execa';

export interface DefaultIdentityDependencies {
readonly mkdir: typeof fs.mkdir;
readonly writeFile: typeof fs.writeFile;
readonly readFile: typeof fs.readFile;
readonly rm: typeof fs.rm;
readonly rmdir: typeof fs.rmdir;
readonly lstat: typeof fs.lstat;
readonly run: (
command: string,
args: readonly string[],
) => Promise<{ stdout: string; stderr: string }>;
readonly sleep: (milliseconds: number) => Promise<void>;
readonly pid: number;
readonly processStartTime: (pid: number) => Promise<string | undefined>;
}

/**
* Creates hardened filesystem and process helpers for privileged identity and lock management.
* Commands receive only a fixed system PATH, while callers provide their process start-time reader.
*/
export function createDefaultIdentityDependencies(
processStartTime: DefaultIdentityDependencies['processStartTime'],
): DefaultIdentityDependencies {
return {
mkdir: fs.mkdir,
writeFile: fs.writeFile,
readFile: fs.readFile,
rm: fs.rm,
rmdir: fs.rmdir,
lstat: fs.lstat,
run: async (command, args) => {
// Identity tool paths are absolute paths returned by the root-only preflight.
// eslint-disable-next-line local/no-unsafe-execa
const result = await execa(command, [...args], {
reject: false,
stdio: ['ignore', 'pipe', 'pipe'],
env: { PATH: '/usr/sbin:/usr/bin:/sbin:/bin' },
extendEnv: false,
});
if (result.exitCode !== 0) {
throw new Error(
`${command} ${args.join(' ')} exited with code ${result.exitCode}: ` +
`${result.stderr.trim() || result.stdout.trim()}`,
);
}
return { stdout: result.stdout, stderr: result.stderr };
},
sleep: (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)),
pid: process.pid,
processStartTime,
};
}
27 changes: 2 additions & 25 deletions src/nvx/runtime-lifecycle.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { randomBytes } from 'crypto';
import { promises as fs } from 'fs';
import * as path from 'path';
import execa from 'execa';
import { createDefaultIdentityDependencies } from '../identity-dependencies';
import {
createMicrovmNetworkPlan,
generateMicrovmNftRuleset,
Expand Down Expand Up @@ -100,31 +100,8 @@ export interface NvxPhase3dLaunchPlan {
}

const defaultDependencies: NvxRuntimeLifecycleDependencies = {
mkdir: fs.mkdir,
writeFile: fs.writeFile,
readFile: fs.readFile,
rm: fs.rm,
...createDefaultIdentityDependencies(readProcessStartTime),
rename: fs.rename,
rmdir: fs.rmdir,
lstat: fs.lstat,
run: async (command, args) => {
const result = await execa(command, [...args], {
reject: false,
stdio: ['ignore', 'pipe', 'pipe'],
env: { PATH: '/usr/sbin:/usr/bin:/sbin:/bin' },
extendEnv: false,
});
if (result.exitCode !== 0) {
throw new Error(
`${command} ${args.join(' ')} exited with code ${result.exitCode}: ` +
`${result.stderr.trim() || result.stdout.trim()}`,
);
}
return { stdout: result.stdout, stderr: result.stderr };
},
sleep: (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)),
pid: process.pid,
processStartTime: readProcessStartTime,
};

interface NvxDeviceAclGrant {
Expand Down
Loading