Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
cli: add flags for access control
Add `--disable=featureA,featureB,...` and
`--experimental-access-control` flags that control
(and enable) `process.accessControl` at startup.
  • Loading branch information
addaleax committed Aug 19, 2018
commit b47f3ebda0d9211308c7aebaedc99bc9a906fd0b
18 changes: 18 additions & 0 deletions doc/api/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,14 @@ If this flag is passed, the behavior can still be set to not abort through
[`process.setUncaughtExceptionCaptureCallback()`][] (and through usage of the
`domain` module that uses it).

### `--disable=...`
<!-- YAML
added: REPLACEME
-->

Disable certain features of Node.js at startup. See [`process.accessControl`][]
for more details.

### `--enable-fips`
<!-- YAML
added: v6.0.0
Expand All @@ -60,6 +68,15 @@ added: v6.0.0
Enable FIPS-compliant crypto at startup. (Requires Node.js to be built with
`./configure --openssl-fips`.)

### `--experimental-access-control`
<!-- YAML
added: REPLACEME
-->

Allow disabling certain features of Node.js at runtime.
See [`process.accessControl`][] for more details.
The `--disable` flag implies this flag.

### `--experimental-modules`
<!-- YAML
added: v8.5.0
Expand Down Expand Up @@ -688,6 +705,7 @@ greater than `4` (its current default value). For more information, see the
[`--openssl-config`]: #cli_openssl_config_file
[`Buffer`]: buffer.html#buffer_class_buffer
[`SlowBuffer`]: buffer.html#buffer_class_slowbuffer
[`process.accessControl`]: process.html#process_access_control
[`process.setUncaughtExceptionCaptureCallback()`]: process.html#process_process_setuncaughtexceptioncapturecallback_fn
[Chrome DevTools Protocol]: https://chromedevtools.github.io/devtools-protocol/
[REPL]: repl.html
Expand Down
7 changes: 7 additions & 0 deletions doc/api/process.md
Original file line number Diff line number Diff line change
Expand Up @@ -2000,6 +2000,13 @@ Will generate an object similar to:

> Stability: 1 - Experimental

This feature is experimental and needs to be enabled by passing
the `--experimental-access-control` flag, or the
`--disable=restrictionA,restrictionB,...` flag to Node.js.

The `--disable` flag takes a comma-separated list of identifiers as described
below. For example, it can be used as `--disable=fsRead,fsWrite`.

### process.accessControl.apply(restrictions)
<!-- YAML
added: REPLACEME
Expand Down
12 changes: 12 additions & 0 deletions doc/node.1
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,12 @@ the next argument will be used as a script filename.
.It Fl -abort-on-uncaught-exception
Aborting instead of exiting causes a core file to be generated for analysis.
.
.It Fl -disable Ns = Ns Ar featureA,featureB,...
Disables certain features of Node.js at startup.
A full list of available restriction identifiers can be shown by running:
.Pp
.Sy ./node --experimental-access-control --print 'process.accessControl.getCurrent()'
.
.It Fl -enable-fips
Enable FIPS-compliant crypto at startup.
Requires Node.js to be built with
Expand All @@ -83,6 +89,12 @@ Requires Node.js to be built with
.It Fl -experimental-modules
Enable experimental ES module support and caching modules.
.
.It Fl -experimental-access-control
Allow disabling certain features of Node.js at runtime.
The
.Fl -disable
flag implies this flag.
.
.It Fl -experimental-repl-await
Enable experimental top-level
.Sy await
Expand Down
3 changes: 2 additions & 1 deletion lib/internal/bootstrap/node.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,8 @@
perThreadSetup.setupMemoryUsage(_memoryUsage);
perThreadSetup.setupKillAndExit();

process.accessControl = internalBinding('access_control');
if (process.binding('config').experimentalAccessControl)
process.accessControl = internalBinding('access_control');

if (global.__coverage__)
NativeModule.require('internal/process/write-coverage').setup();
Expand Down
2 changes: 2 additions & 0 deletions src/env.cc
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,8 @@ Environment::Environment(IsolateData* isolate_data,

isolate()->GetHeapProfiler()->AddBuildEmbedderGraphCallback(
BuildEmbedderGraph, this);

access_control()->apply(global_access_control);
}

Environment::~Environment() {
Expand Down
2 changes: 2 additions & 0 deletions src/env.h
Original file line number Diff line number Diff line change
Expand Up @@ -457,6 +457,8 @@ class AccessControl {
v8::MaybeLocal<v8::Object> ToObject(v8::Local<v8::Context> context);
static v8::Maybe<AccessControl> FromObject(v8::Local<v8::Context> context,
v8::Local<v8::Object> object);
static AccessControl FromString(const std::string& disabled_item_list,
std::string* unknown_item);

static void ThrowAccessDenied(Environment* env, Permission perm);

Expand Down
32 changes: 32 additions & 0 deletions src/node.cc
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,12 @@ bool config_preserve_symlinks = false;
// that is used by lib/module.js
bool config_preserve_symlinks_main = false;

// Set in node.cc by ParseArgs when --experimental-access-control is used.
// Used in node_config.cc to set a constant on process.binding('config')
// that is used by the bootstrapper.
bool config_experimental_access_control = false;
AccessControl global_access_control;

// Set in node.cc by ParseArgs when --experimental-modules is used.
// Used in node_config.cc to set a constant on process.binding('config')
// that is used by lib/module.js
Expand Down Expand Up @@ -2615,9 +2621,15 @@ static void PrintHelp() {
" --abort-on-uncaught-exception\n"
" aborting instead of exiting causes a\n"
" core file to be generated for analysis\n"
" --disable=featureA,featureB,...\n"
" experimental support for\n"
" disabling individual features\n"
" by listing them on the command line\n"
#if HAVE_OPENSSL && NODE_FIPS_MODE
" --enable-fips enable FIPS crypto at startup\n"
#endif // NODE_FIPS_MODE && NODE_FIPS_MODE
" --experimental-access-control experimental support for\n"
" disabling individual features\n"
" --experimental-modules experimental ES Module support\n"
" and caching modules\n"
" --experimental-repl-await experimental await keyword support\n"
Expand Down Expand Up @@ -2785,7 +2797,9 @@ static void CheckIfAllowedInEnv(const char* exe, bool is_env,
static const char* whitelist[] = {
// Node options, sorted in `node --help` order for ease of comparison.
// Please, update NODE_OPTIONS section in cli.md if changed.
"--disable",
"--enable-fips",
"--experimental-access-control",
"--experimental-modules",
"--experimental-repl-await",
"--experimental-vm-modules",
Expand Down Expand Up @@ -2982,6 +2996,18 @@ static void ParseArgs(int* argc,
config_preserve_symlinks = true;
} else if (strcmp(arg, "--preserve-symlinks-main") == 0) {
config_preserve_symlinks_main = true;
} else if (strncmp(arg, "--disable=", 10) == 0) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this flag seems a bit too generic

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, it is generic in that it allows a wide variety of features to be disabled. Do you prefer something like --disable-features=…? That doesn’t quite seem to add anything here, except make it a bit more work to spell out (which, you know, can totally be something we want).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

--disable-features seems a bit better to me. i'm just looking at the flag in the context of all the other options. perhaps some bikeshedding can happen here.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I won't bikeshed the name too much but I'm a fan of simple and concise... --deny= and --allow= would be just fine for me. Including --allow= opens the possibility that later on we can flip the defaults on certain items, having them off by default.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tbh, I’m not quite convinced that we’d ever want to have something off by default here (other than experimental features, for which we already have flags and which aren’t really the concern here…)

std::string unknown_item;
global_access_control.apply(
AccessControl::FromString(arg + 10, &unknown_item));
if (!unknown_item.empty()) {
fprintf(stderr, "%s: unknown --disable entry %s\n",
argv[0], unknown_item.c_str());
exit(9);
}
config_experimental_access_control = true;
} else if (strcmp(arg, "--experimental-access-control") == 0) {
config_experimental_access_control = true;
} else if (strcmp(arg, "--experimental-modules") == 0) {
config_experimental_modules = true;
} else if (strcmp(arg, "--experimental-vm-modules") == 0) {
Expand Down Expand Up @@ -3081,6 +3107,12 @@ static void ParseArgs(int* argc,
exit(9);
}

if (config_experimental_access_control) {
fprintf(stderr,
"Warning: Access control through disabling features is "
"experimental and may be changed at any time.\n");
}

// Copy remaining arguments.
const unsigned int args_left = nargs - index;

Expand Down
18 changes: 18 additions & 0 deletions src/node_access_control.cc
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,24 @@ Maybe<AccessControl> AccessControl::FromObject(Local<Context> context,
return Just(ret);
}

AccessControl AccessControl::FromString(const std::string& disabled_item_list,
std::string* unknown_item) {
AccessControl ret;

std::set<std::string> items = ParseCommaSeparatedSet(disabled_item_list);
for (const std::string& item : items) {
Permission perm = PermissionFromString(item.c_str());
if (perm != kNumPermissions) {
ret.set_permission(perm, false);
continue;
}
if (unknown_item != nullptr)
*unknown_item = item;
}

return ret;
}

AccessControl::Permission AccessControl::PermissionFromString(const char* str) {
#define V(kind) if (strcmp(str, #kind) == 0) return kind;
ACCESS_CONTROL_FLAGS(V)
Expand Down
3 changes: 3 additions & 0 deletions src/node_config.cc
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@ static void Initialize(Local<Object> target,
if (config_preserve_symlinks_main)
READONLY_BOOLEAN_PROPERTY("preserveSymlinksMain");

if (config_experimental_access_control)
READONLY_BOOLEAN_PROPERTY("experimentalAccessControl");

if (config_experimental_modules) {
READONLY_BOOLEAN_PROPERTY("experimentalModules");
if (!config_userland_loader.empty()) {
Expand Down
6 changes: 6 additions & 0 deletions src/node_internals.h
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,12 @@ extern bool config_preserve_symlinks;
// that is used by lib/module.js
extern bool config_preserve_symlinks_main;

// Set in node.cc by ParseArgs when --experimental-access-control is used.
// Used in node_config.cc to set a constant on process.binding('config')
// that is used by the bootstrapper.
extern bool config_experimental_access_control;
extern AccessControl global_access_control;

// Set in node.cc by ParseArgs when --experimental-modules is used.
// Used in node_config.cc to set a constant on process.binding('config')
// that is used by lib/module.js
Expand Down
1 change: 1 addition & 0 deletions test/parallel/test-access-control.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// Flags: --experimental-access-control
'use strict';
const common = require('../common');
const tmpdir = require('../common/tmpdir');
Expand Down
46 changes: 46 additions & 0 deletions test/parallel/test-cli-access-control.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
'use strict';
require('../common');
const assert = require('assert');
const { spawnSync } = require('child_process');

assert.strictEqual(
spawnSync(process.execPath, ['-p', 'process.accessControl'])
.stdout.toString().trim(), 'undefined');

{
const { stdout, stderr } = spawnSync(
process.execPath,
['--experimental-access-control', '-p', 'process.accessControl.apply']);
assert.strictEqual(stdout.toString().trim(), '[Function: apply]');
assert.strictEqual(
stderr.toString().trim(),
'Warning: Access control through disabling features is experimental and ' +
'may be changed at any time.');
}

{
const { stdout, stderr } = spawnSync(
process.execPath,
['--disable=fsRead', '-p', 'process.accessControl.apply']);
assert.strictEqual(stdout.toString().trim(), '[Function: apply]');
assert.strictEqual(
stderr.toString().trim(),
'Warning: Access control through disabling features is experimental and ' +
'may be changed at any time.');
}

{
const { stderr } = spawnSync(process.execPath, ['--disable=unknown']);
assert.strictEqual(
stderr.toString().trim(),
`${process.execPath}: unknown --disable entry unknown`);
}

{
const { stderr } = spawnSync(
process.execPath,
['--disable=fsRead', '-p', 'fs.readFileSync(process.execPath)']);
assert.ok(
stderr.toString().includes('Access to this API has been restricted'),
stderr);
}
2 changes: 1 addition & 1 deletion test/parallel/test-worker-access-control.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Flags: --experimental-worker
// Flags: --experimental-worker --experimental-access-control
'use strict';
const common = require('../common');
const assert = require('assert');
Expand Down