mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-08-16 04:06:44 +00:00
ci: harden the fusermount3 repair (#10485)
* ci: move the fusermount3 repair into a composite action Three copies of the same block were already drifting apart, and the target comes from PATH: only ever add setuid root to a root-owned, non-symlink binary under the system bin paths, and say why otherwise. * test: say that the process exited in the wait errors "process exit status 1 before ... accepted connections" is missing its verb. Also mark the SIGTERM return discarded - it fails with os.ErrProcessDone exactly when the select below already handles it. * ci: prefer the distro fusermount3 over escalating a shadow copy The shadowing /usr/local/bin/fusermount3 is not root-owned either, so setting its setuid bit would have handed root to a binary the runner user owns - the repair now symlinks the distro one earlier in PATH and touches nothing, keeping the in-place chmod for a root-owned binary with no distro alternative. A setuid bit only grants root when root owns the file, so accept an existing one only then. * ci: run the FUSE workflows when the shared action changes Their paths filters listed each workflow file but not the composite action all three now call.
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
name: Fix fusermount3 setuid
|
||||
description: >
|
||||
Make sure the fusermount3 an unprivileged mount will find can actually mount.
|
||||
Some runner images carry a second, source-built fusermount3 in /usr/local/bin
|
||||
that shadows the distro one in PATH; it is neither setuid nor root-owned, so
|
||||
every mount fails with "mount failed: Operation not permitted". Both the Go
|
||||
mount and sw-fuse resolve the helper through PATH.
|
||||
Run it after the step that apt-installs fuse3.
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Point PATH at a fusermount3 that can mount
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
# setuid only grants root when root owns the file, and exec follows
|
||||
# symlinks, so judge the target.
|
||||
can_mount() {
|
||||
[ -u "$1" ] && [ "$(stat -Lc %u "$1")" = 0 ]
|
||||
}
|
||||
|
||||
bin=$(command -v fusermount3 || true)
|
||||
if [ -z "$bin" ]; then
|
||||
echo "no fusermount3 in PATH" >&2
|
||||
exit 1
|
||||
fi
|
||||
if can_mount "$bin"; then
|
||||
ls -l "$bin"
|
||||
exit 0
|
||||
fi
|
||||
echo "$bin cannot mount unprivileged:"
|
||||
ls -l "$bin"
|
||||
|
||||
# The distro fusermount3 is setuid root and is the one meant to be used.
|
||||
# Reach it through a symlink earlier in PATH: exec resolves the link, so
|
||||
# the target keeps its setuid bit, and nothing on the image is modified.
|
||||
for distro in /usr/bin/fusermount3 /bin/fusermount3; do
|
||||
if [ "$distro" != "$bin" ] && can_mount "$distro"; then
|
||||
mkdir -p "$RUNNER_TEMP/fuse-bin"
|
||||
ln -sf "$distro" "$RUNNER_TEMP/fuse-bin/fusermount3"
|
||||
echo "$RUNNER_TEMP/fuse-bin" >> "$GITHUB_PATH"
|
||||
echo "using $distro instead"
|
||||
ls -l "$distro"
|
||||
exit 0
|
||||
fi
|
||||
done
|
||||
|
||||
# No usable distro binary, so setting the bit is the only way out - but
|
||||
# the target came from PATH: do that only for a root-owned system binary,
|
||||
# never for anything else that happens to sit there.
|
||||
case "$bin" in
|
||||
/bin/*|/sbin/*|/usr/bin/*|/usr/sbin/*|/usr/local/bin/*|/usr/local/sbin/*) ;;
|
||||
*) echo "refusing to setuid $bin: outside the system bin paths" >&2; exit 1 ;;
|
||||
esac
|
||||
if [ -L "$bin" ] || [ ! -f "$bin" ] || [ ! -x "$bin" ]; then
|
||||
echo "refusing to setuid $bin: not a regular executable file" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ "$(stat -c %u "$bin")" != 0 ] || [ "$(stat -c %g "$bin")" != 0 ]; then
|
||||
echo "refusing to setuid $bin: not owned by root:root" >&2
|
||||
exit 1
|
||||
fi
|
||||
sudo chmod u+s "$bin"
|
||||
ls -l "$bin"
|
||||
@@ -8,6 +8,7 @@ on:
|
||||
- 'weed/cluster/**'
|
||||
- 'test/fuse_dlm/**'
|
||||
- '.github/workflows/fuse-dlm-integration.yml'
|
||||
- '.github/actions/fix-fusermount-setuid/**'
|
||||
push:
|
||||
branches: [master]
|
||||
paths:
|
||||
@@ -15,6 +16,7 @@ on:
|
||||
- 'weed/mount/**'
|
||||
- 'weed/cluster/**'
|
||||
- 'test/fuse_dlm/**'
|
||||
- '.github/actions/fix-fusermount-setuid/**'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.head_ref || github.ref }}/fuse-dlm-integration
|
||||
@@ -44,18 +46,9 @@ jobs:
|
||||
sudo apt-get install -y libfuse3-dev
|
||||
echo 'user_allow_other' | sudo tee -a /etc/fuse.conf
|
||||
sudo chmod 644 /etc/fuse.conf
|
||||
# Some runner images carry a second, source-built fusermount3 in
|
||||
# /usr/local/bin that shadows the distro one in PATH without its setuid
|
||||
# bit, and every unprivileged mount then fails with EPERM. go-fuse takes
|
||||
# the first fusermount3 in PATH, so repair that one.
|
||||
fusermount_bin=$(command -v fusermount3 || true)
|
||||
if [ -n "$fusermount_bin" ]; then
|
||||
if [ ! -u "$fusermount_bin" ]; then
|
||||
sudo chown root:root "$fusermount_bin"
|
||||
sudo chmod u+s "$fusermount_bin"
|
||||
fi
|
||||
ls -l "$fusermount_bin"
|
||||
fi
|
||||
|
||||
- name: Repair the fusermount3 setuid bit
|
||||
uses: ./.github/actions/fix-fusermount-setuid
|
||||
|
||||
- name: Build SeaweedFS
|
||||
run: go build -o weed/weed -buildvcs=false ./weed
|
||||
|
||||
@@ -7,12 +7,14 @@ on:
|
||||
- 'weed/**'
|
||||
- 'test/fuse_integration/**'
|
||||
- '.github/workflows/fuse-integration.yml'
|
||||
- '.github/actions/fix-fusermount-setuid/**'
|
||||
pull_request:
|
||||
branches: [ master, main ]
|
||||
paths:
|
||||
- 'weed/**'
|
||||
- 'test/fuse_integration/**'
|
||||
- '.github/workflows/fuse-integration.yml'
|
||||
- '.github/actions/fix-fusermount-setuid/**'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.head_ref }}/fuse-integration
|
||||
@@ -45,22 +47,13 @@ jobs:
|
||||
# Allow non-root FUSE mounts with allow_other
|
||||
echo 'user_allow_other' | sudo tee -a /etc/fuse.conf
|
||||
sudo chmod 644 /etc/fuse.conf
|
||||
# Some runner images carry a second, source-built fusermount3 in
|
||||
# /usr/local/bin that shadows the distro one in PATH without its setuid
|
||||
# bit, and every unprivileged mount then fails with EPERM. go-fuse takes
|
||||
# the first fusermount3 in PATH, so repair that one.
|
||||
fusermount_bin=$(command -v fusermount3 || true)
|
||||
if [ -n "$fusermount_bin" ]; then
|
||||
if [ ! -u "$fusermount_bin" ]; then
|
||||
sudo chown root:root "$fusermount_bin"
|
||||
sudo chmod u+s "$fusermount_bin"
|
||||
fi
|
||||
ls -l "$fusermount_bin"
|
||||
fi
|
||||
# Verify FUSE installation
|
||||
fusermount3 --version || fusermount --version || true
|
||||
ls -la /dev/fuse
|
||||
|
||||
- name: Repair the fusermount3 setuid bit
|
||||
uses: ./.github/actions/fix-fusermount-setuid
|
||||
|
||||
- name: Build SeaweedFS
|
||||
run: |
|
||||
cd weed
|
||||
|
||||
@@ -11,6 +11,7 @@ on:
|
||||
- 'weed/pb/filer.proto'
|
||||
- 'test/fuse_p2p/**'
|
||||
- '.github/workflows/fuse-p2p-integration.yml'
|
||||
- '.github/actions/fix-fusermount-setuid/**'
|
||||
push:
|
||||
branches: [master]
|
||||
paths:
|
||||
@@ -21,6 +22,7 @@ on:
|
||||
- 'weed/pb/mount_peer.proto'
|
||||
- 'weed/pb/filer.proto'
|
||||
- 'test/fuse_p2p/**'
|
||||
- '.github/actions/fix-fusermount-setuid/**'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.head_ref || github.ref }}/fuse-p2p-integration
|
||||
@@ -50,18 +52,9 @@ jobs:
|
||||
sudo apt-get install -y libfuse3-dev
|
||||
echo 'user_allow_other' | sudo tee -a /etc/fuse.conf
|
||||
sudo chmod 644 /etc/fuse.conf
|
||||
# Some runner images carry a second, source-built fusermount3 in
|
||||
# /usr/local/bin that shadows the distro one in PATH without its setuid
|
||||
# bit, and every unprivileged mount then fails with EPERM. go-fuse takes
|
||||
# the first fusermount3 in PATH, so repair that one.
|
||||
fusermount_bin=$(command -v fusermount3 || true)
|
||||
if [ -n "$fusermount_bin" ]; then
|
||||
if [ ! -u "$fusermount_bin" ]; then
|
||||
sudo chown root:root "$fusermount_bin"
|
||||
sudo chmod u+s "$fusermount_bin"
|
||||
fi
|
||||
ls -l "$fusermount_bin"
|
||||
fi
|
||||
|
||||
- name: Repair the fusermount3 setuid bit
|
||||
uses: ./.github/actions/fix-fusermount-setuid
|
||||
|
||||
- name: Build SeaweedFS
|
||||
run: go build -o weed/weed -buildvcs=false ./weed
|
||||
|
||||
@@ -219,7 +219,7 @@ func (p *managedProcess) exited() error {
|
||||
if p.err != nil {
|
||||
return p.err
|
||||
}
|
||||
return fmt.Errorf("exited with status 0")
|
||||
return fmt.Errorf("exit status 0")
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
@@ -227,7 +227,9 @@ func (p *managedProcess) exited() error {
|
||||
|
||||
// stop asks the process to terminate and waits for it to go away.
|
||||
func (p *managedProcess) stop() {
|
||||
p.cmd.Process.Signal(syscall.SIGTERM)
|
||||
// Signal fails with os.ErrProcessDone when the child is already gone, which
|
||||
// is exactly the case the select below handles.
|
||||
_ = p.cmd.Process.Signal(syscall.SIGTERM)
|
||||
select {
|
||||
case <-p.done:
|
||||
case <-time.After(10 * time.Second):
|
||||
@@ -379,7 +381,7 @@ func (f *FuseTestFramework) waitForService(proc *managedProcess, addr string, ti
|
||||
return nil
|
||||
}
|
||||
if exitErr := proc.exited(); exitErr != nil {
|
||||
return fmt.Errorf("process %v before %s accepted connections", exitErr, addr)
|
||||
return fmt.Errorf("process exited (%v) before %s accepted connections", exitErr, addr)
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
@@ -406,7 +408,7 @@ func (f *FuseTestFramework) waitForMount(timeout time.Duration) error {
|
||||
// A mount that cannot mount at all (no /dev/fuse, fusermount not setuid)
|
||||
// dies within a second; reporting that beats waiting out the timeout.
|
||||
if exitErr := f.mountProcess.exited(); exitErr != nil {
|
||||
return fmt.Errorf("mount process %v", exitErr)
|
||||
return fmt.Errorf("mount process exited (%v)", exitErr)
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user