当 运行 NPM 8 作为 root 时获得 EACCESS

Getting EACCESS when running NPM 8 as root

tldr;使用 fs.readFileSync 的脚本在使用 npm 但不使用 node

调用时抛出 EACCESS

在一个古老的 (2016) Docker 图像上,我需要 运行 一个涉及 Bower (bower install --allow-root) 的 postinstall NPM 脚本,但每当我这样做时,我得到 EACCES: permission denied, open '/root/.config/configstore/bower-github.json'。我发现做 npx bower 的结果是一样的。 运行 npx bower 在 Docker 之外 工作正常。

通常,我会很容易地处理这些问题,因为它们通常会在有人使用 sudo 执行命令时出现,而他们不应该这样做。这些问题的解决方法通常是将所有者更改回当前用户,或者只是 运行 带 sudo 和 --allow-root (example 1, example 2).

的 bower 命令

然而,这不是这些问题之一。 我已经是 root 了!

完整错误类似于任何类似问题:

root@eaa32456c249:/var/www/myproj# npx bower --allow-root
/var/www/myproj/node_modules/bower/lib/node_modules/configstore/index.js:54
                throw err;
                ^

Error: EACCES: permission denied, open '/root/.config/configstore/bower-github.json'
You don't have access to this file.

    at Object.openSync (node:fs:585:3)
    at Object.readFileSync (node:fs:453:35)
    at Configstore.get (/var/www/myproj/node_modules/bower/lib/node_modules/configstore/index.js:35:38)
    at new Configstore (/var/www/myproj/node_modules/bower/lib/node_modules/configstore/index.js:28:48)
    at readCachedConfig (/var/www/myproj/node_modules/bower/lib/config.js:19:23)
    at defaultConfig (/var/www/myproj/node_modules/bower/lib/config.js:11:12)
    at Object.<anonymous> (/var/www/myproj/node_modules/bower/lib/index.js:16:32)
    at Module._compile (node:internal/modules/cjs/loader:1101:14)
    at Object.Module._extensions..js (node:internal/modules/cjs/loader:1153:10)
    at Module.load (node:internal/modules/cjs/loader:981:32) {
  errno: -13,
  syscall: 'open',
  code: 'EACCES',
  path: '/root/.config/configstore/bower-github.json'

我无法进一步提升我的权利,添加 --allow-root 也没有任何作用。我什至检查了有问题的模块,看到总是失败的调用就是这样:

readFileSync(this.path, 'utf8');

其中 this.path 当然是 '/root/.config/configstore/bower-github.json'

然后我写了这个做同样事情的小测试模块,它 运行 没有问题:

root@eaa32456c249:/var/www/myproj# cat test.js
const execSync = require('child_process').execSync;
const fs = require('fs');
const path = '/root/.config/configstore/bower-github.json';

console.log('exec whoami: ', execSync('whoami').toString());
try {
    const result = execSync('ls -l ' + path, { encoding: 'utf8' });
    console.log('exec ls -l: ', result);
} catch (err) {}

try {
    const parsed = JSON.parse(fs.readFileSync(path, 'utf8', { encoding: 'utf8' }));
    console.log('parsed: ', parsed);
} catch (err) {
    console.error(err.message);
}
root@eaa32456c249:/var/www/myproj# node test.js
exec whoami:  root

exec ls -l:  -rw-r--r-- 1 root root 3 Dec  8 22:55 /root/.config/configstore/bower-github.json

parsed:  {}

一个谜!

tldr; NPM 版本 > 6 将 运行 作为根包目录的所有者。换句话说,如果你想 运行 作为 root,在根目录上做 chown root.root -R .


让它慢炖一段时间后,我只是想我可以检查代码是谁 运行ning as,所以我打开了有问题的模块 (node_modules/configstore/index.js) 并将其添加到失败调用之前的行:

const execSync = require('child_process').execSync;
console.log('exec `id`: ', execSync('id', { encoding: 'utf8' }));
console.log('exec `ls`: ', execSync('ls -l /root', { encoding: 'utf8' }));

确实发生了一些可疑的事情,正如打印的那些行:

exec `id`:  uid=1000 gid=1000 groups=1000

ls: cannot open directory '/root': Permission denied

那么,运行宁 npx bower 作为 root 使得 bower 运行 成为 uid=1000 的用户? 运行 npm run postinstall 导致同样的问题。

好的...让我们仔细看看这个。如果我使用 node 手动 运行 bower CLI 模块怎么办?

$ node node_modules/.bin/bower  --allow-root
root@eaa32456c249:/var/www/myproj# node node_modules/.bin/bower  --allow-root
exec `id`:  uid=0(root) gid=0(root) groups=0(root)

exec `ls`:  total 0

有效 - 我仍然是 root!所以很明显 npxnpm 都在以某种方式在幕后做一些时髦的事情,关于命令是谁 运行ning as!

NPM,root 和 CWD 的所有者

Digging deep to find the solution

在我发现上述事实后,我进行了一些谷歌搜索并遇到了 this NPM issue,但实际上并没有直接的解释让我追踪到 Node 试图作为文件所有者执行的踪迹.我第一次真正检查了文件的所有者是谁:

root@eaa32456c249:/var/www/myproj# ls -lh node_modules/bower/
total 72K
-rw-r--r-- 1 1000 1000  40K Oct 20 08:50 CHANGELOG.md
-rw-r--r-- 1 1000 1000 1.1K Oct 20 08:50 LICENSE
-rw-r--r-- 1 1000 1000  14K Oct 20 08:50 README.md
drwxr-xr-x 2 1000 1000 4.0K Oct 20 08:50 bin
drwxr-xr-x 9 1000 1000 4.0K Oct 20 08:50 lib
-rw-r--r-- 1 1000 1000  460 Oct 20 08:50 package.json

只是做 chown root.root -R node_modules 什么也没做,所以我继续搜索。然后我读了 this article,其中有这个片段:

If npm was invoked with root privileges, then it will change the uid to the user account or uid specified by the user config, which defaults to nobody. Set the unsafe-perm flag to run scripts with root privileges.

好的,让我们尝试将 unsafe-perm 设置为 true。不行,我还是运行ning as uid=1000。然后,我冒险进入实际的 NPM 库,搜索与 uid 相关的内容,并在以下位置找到了答案:

 /usr/local/lib/node_modules/npm/node_modules/@npmcli/promise-spawn/index.js

对于 NPM 8,它使用这段代码来确定谁 运行 作为:

const { uid, gid } = isRoot ? inferOwner.sync(cwd) : {}

并且正如 infer-owner 模块的 the docs 所说:

Infer the owner of a path based on the owner of its nearest existing parent

注意 cwd 部分。它看的不是node_modules,而是当前的工作目录!所以我然后在根目录上做了 chown root.root -R .,你看,它成功了!

附录

此行为仅适用于 >= 7 的 NPM 版本。当 运行以 root 身份运行时,Node 12-14 随附的 NPM 版本 < 7 工作得非常好。因此,最快的解决方法可能就是使用 Node 14。