docker 以根用户身份运行NPM 8时获取EACCESS

kuarbcqp  于 2022-12-18  发布在  Docker
关注(0)|答案(1)|浏览(135)

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的结果是一样的。
通常情况下,我会很容易地处理这些问题,因为当有人使用sudo执行不应该执行的命令时,通常会出现这些问题。解决这些问题的方法通常是将所有者更改回当前用户,或者使用sudo和--allow-rootexample 1example 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:  {}

一个谜!

elcex8rz

elcex8rz1#

**tldr;**NPM版本〉6(但小于9!)将以根包目录的所有者身份运行。换句话说,如果你想以根目录的身份运行,在你项目的根目录上执行chown root.root -R .
更新NPM 9恢复了这一变更,意味着上述内容仅适用于NPM 6和7。参见PR。

在让这个问题酝酿了很长一段时间之后,我想我可能会检查代码是以谁的身份运行的,所以我打开了有问题的模块(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

因此,以root的身份运行npx bower会使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

它工作-我仍然是根!所以很明显,npxnpm都在不知何故做一些时髦的掩盖下,关于谁的命令运行!

NPM、root和CWD的所有者

深入挖掘以找到解决方案
在我发现上述事实后,我做了一些谷歌搜索,并遇到了this NPM issue,而实际上没有一个直接的解释,使我进入节点试图作为文件的所有者执行的线索。

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,其中包含以下代码片段:
如果使用root权限调用npm,则它会将uid更改为用户帐户或用户config指定的uid,默认值为nobody。设置unsafe-perm标志以使用root权限运行脚本。
好的,让我们尝试将unsafe-perm设置为true。没有成功,我仍然以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所述:
根据路径最近的现有父路径的所有者推断路径的所有者
注意cwd部分。它不查看node_modules,而是当前工作目录!所以我在项目的根目录上运行了chown root.root -R .,你瞧,它成功了!

附录

此行为仅适用于NPM版本7和8。NPM版本〈7(随节点12-14一起提供)在以root身份运行时工作得非常好。因此,最快的修复方法可能只是使用节点14。从NPM 9开始,旧的行为又回来了(可能随节点20一起提供)。

相关问题