当 运行 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!所以很明显 npx
和 npm
都在以某种方式在幕后做一些时髦的事情,关于命令是谁 运行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。
tldr;使用 fs.readFileSync 的脚本在使用 npm
但不使用 node
在一个古老的 (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).
然而,这不是这些问题之一。 我已经是 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!所以很明显 npx
和 npm
都在以某种方式在幕后做一些时髦的事情,关于命令是谁 运行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。