Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(endomoat): policy generation implementation #969

Draft
wants to merge 1 commit into
base: endo-refactor-gutted
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions packages/endomoat/package.json
Expand Up @@ -42,6 +42,7 @@
"@endo/compartment-mapper": "1.1.4",
"@endo/evasive-transform": "1.0.4",
"@types/node": "18.19.28",
"json-stable-stringify": "1.1.1",
"lavamoat-core": "^15.3.0",
"ses": "1.4.1",
"type-fest": "4.14.0",
Expand All @@ -50,6 +51,7 @@
"devDependencies": {
"@endo/eslint-plugin": "2.1.0",
"@jessie.js/eslint-plugin": "0.4.0",
"@types/json-stable-stringify": "1.0.36",
"memfs": "4.8.1"
},
"publishConfig": {
Expand Down
73 changes: 71 additions & 2 deletions packages/endomoat/src/cli.js
Expand Up @@ -17,9 +17,16 @@ import assert from 'node:assert'
import path from 'node:path'
import yargs from 'yargs'
import { hideBin } from 'yargs/helpers'
import { constants, loadPolicies, run } from './index.js'
import {
constants,
generateAndWritePolicy,
loadPolicies,
run,
} from './index.js'
import { readJsonFile } from './util.js'

const BEHAVIOR_GROUP = 'Behavior Options:'

/**
* Main entry point to CLI
*/
Expand Down Expand Up @@ -169,8 +176,70 @@ async function main(args = hideBin(process.argv)) {
await run(argv.entrypoint, policy)
}
)
.demandCommand(1)
.command(
['gen <entrypoint>', 'generate <entrypoint>'],
'Generate policy files; overwrites existing policies',
(yargs) =>
yargs
.options({
run: {
describe: 'Run the application after policy generated',
type: 'boolean',
group: BEHAVIOR_GROUP,
},
debug: {
type: 'boolean',
describe: 'Additionally write a debug policy',
group: BEHAVIOR_GROUP,
},
})
.positional('entrypoint', {
describe: 'Path to the application entry point',
type: 'string',
normalize: true,
coerce: path.resolve,
})
.demandOption('entrypoint')
/**
* Resolve entrypoint from `cwd`
*/
.middleware((argv) => {
argv.entrypoint = path.resolve(argv.cwd, argv.entrypoint)
}, true)
/**
* This should not fail. If it does, there is a bug.
*/
.check((argv) => {
assert(
path.isAbsolute(argv.entrypoint),
'entrypoint must be an absolute path'
)
return true
}),
async ({
entrypoint,
debug,
run: shouldRun,
policy: policyPath,
'policy-debug': policyDebugPath,
}) => {
const policy = await generateAndWritePolicy(entrypoint, {
debug,
policyPath,
policyDebugPath,
})

if (debug) {
console.error(`Wrote debug policy to ${policyDebugPath}`)
}
console.error(`Wrote policy to ${policyPath}`)

if (shouldRun) {
await run(entrypoint, policy)
}
}
)
.demandCommand(1)
.parse()
}

Expand Down
11 changes: 10 additions & 1 deletion packages/endomoat/src/constants.js
Expand Up @@ -3,7 +3,6 @@
*
* _Type a string more than once? Make it a constant!_
*/

import path from 'node:path'

/**
Expand Down Expand Up @@ -56,3 +55,13 @@ export const RSRC_POLICY_BUILTINS = 'builtins'
* Name of the `globals` property of a `LavaMoatPackagePolicy`
*/
export const RSRC_POLICY_GLOBALS = 'globals'

/**
* `builtin` module type for a `LavamoatModuleRecord`
*/
export const LMR_TYPE_BUILTIN = 'builtin'

/**
* `js` module type for a `LavamoatModuleRecord`
*/
export const LMR_TYPE_SOURCE = 'js'
48 changes: 43 additions & 5 deletions packages/endomoat/src/index.js
Expand Up @@ -20,23 +20,61 @@ import { pathToFileURL } from 'node:url'
import { importHook } from './import-hook.js'
import { moduleTransforms } from './module-transforms.js'
import { toEndoPolicy } from './policy-converter.js'
import { defaultReadPowers } from './power.js'
import { generatePolicy } from './policy-gen/index.js'
import { isPolicy } from './policy.js'
import { makeReadPowers } from './power.js'

export * as constants from './constants.js'
export { generateAndWritePolicy, generatePolicy } from './policy-gen/index.js'
export { loadPolicies } from './policy.js'
export { toEndoPolicy }

/**
* Runs a program in Endomoat with the provided policy
* Runs Endomoat with provided policy
*
* @param {string | URL} entrypointPath
* @overload
* @param {string | URL} entryFile
* @param {import('lavamoat-core').LavaMoatPolicy} policy
* @param {import('./types.js').RunOptions} [opts]
* @returns {Promise<unknown>}
*/
export async function run(entrypointPath, policy, opts = {}) {

/**
* Runs Endomoat with an auto-generated policy, optionally writing to disk
*
* @overload
* @param {string | URL} entryFile
* @param {import('./types.js').GenerateAndRunOptions} [opts]
* @returns {Promise<unknown>}
*/

/**
* Runs a program in Endomoat
*
* @param {string | URL} entrypointPath
* @param {import('lavamoat-core').LavaMoatPolicy
* | import('./types.js').GenerateAndRunOptions} [policyOrOpts]
* @param {import('./types.js').RunOptions} [opts]
* @returns {Promise<unknown>}
*/
export async function run(entrypointPath, policyOrOpts = {}, opts = {}) {
await Promise.resolve()
/** @type {import('lavamoat-core').LavaMoatPolicy} */
let policy
/** @type {import('./types.js').RunOptions} */
let runOpts

if (isPolicy(policyOrOpts)) {
policy = policyOrOpts
runOpts = opts
} else {
const generateOpts = policyOrOpts
runOpts = { readPowers: generateOpts.readPowers }
policy = await generatePolicy(entrypointPath, generateOpts)
}

const endoPolicy = toEndoPolicy(policy)
const { readPowers = defaultReadPowers } = opts
const readPowers = makeReadPowers(runOpts.readPowers)

const url =
entrypointPath instanceof URL
Expand Down