From 583a2695a3ddc9c98e0a03d9f1bad8df30afe887 Mon Sep 17 00:00:00 2001 From: Simon Ruderich Date: Sat, 17 Apr 2021 08:10:17 +0200 Subject: [PATCH] Add basic support for FreeBSD Installing packages and starting/enabling services is not yet supported. There are minor limitations when handling symlinks (see README.adoc). --- .builds/freebsd.yml | 9 +++++ README.adoc | 7 ++++ cmd/safcm-remote/ainsl/ainsl_test.go | 9 ++++- cmd/safcm-remote/build.sh | 1 + cmd/safcm-remote/sync/files.go | 11 +++++- cmd/safcm-remote/sync/files_test.go | 39 +++++++++------------- cmd/safcm-remote/sync/filetest/filetest.go | 1 + cmd/safcm/config/files.go | 1 + cmd/safcm/config/files_test.go | 25 ++++++++++++-- rpc/dial.go | 15 ++++++++- 10 files changed, 88 insertions(+), 30 deletions(-) create mode 100644 .builds/freebsd.yml diff --git a/.builds/freebsd.yml b/.builds/freebsd.yml new file mode 100644 index 0000000..1a96cb6 --- /dev/null +++ b/.builds/freebsd.yml @@ -0,0 +1,9 @@ +image: freebsd/latest +packages: + - go + - gmake +tasks: + - all: | + sudo ln -sf /usr/local/bin/gmake /usr/bin/make + cd safcm + ./ci/run diff --git a/README.adoc b/README.adoc index 2471f92..f120694 100644 --- a/README.adoc +++ b/README.adoc @@ -111,6 +111,12 @@ future, others are due to the design of safcm. result is similar -- but not identical -- to quoted strings in regular shell scripts which can be confusing. +- Permissions of symlinks are ignored on BSD systems. They are always shown to + have `0777` as permissions even though the current umask controls the actual + permissions when creating new symlinks. Existing symlinks with different + permissions are not updated. Most BSDs ignore the permissions when following + symlinks which should reduce the impact of this limitation. + == Requirements @@ -128,6 +134,7 @@ future, others are due to the design of safcm. * Supported operating system: ** GNU/Linux with common commands (`uname`, `id`, `stat`, `sha512sum`, `cat`, `mktemp`, `rm`, `ln`, `chmod`) + ** FreeBSD (same commands, but uses `sha512`) * SSH server * to install packages: ** `apt-get` (Debian or derivative) diff --git a/cmd/safcm-remote/ainsl/ainsl_test.go b/cmd/safcm-remote/ainsl/ainsl_test.go index 2526dca..8e9b0c1 100644 --- a/cmd/safcm-remote/ainsl/ainsl_test.go +++ b/cmd/safcm-remote/ainsl/ainsl_test.go @@ -20,6 +20,7 @@ import ( "io/fs" "os" "path/filepath" + "runtime" "syscall" "testing" @@ -53,6 +54,12 @@ func TestHandle(t *testing.T) { } _, uid, _, gid := ft.CurrentUserAndGroup() + symlinkExists := "open file: too many levels of symbolic links" + if runtime.GOOS == "freebsd" { + // EMLINK instead of ELOOP + symlinkExists = "open file: too many links" + } + tests := []struct { name string path string @@ -268,7 +275,7 @@ func TestHandle(t *testing.T) { }, }, nil, - fmt.Errorf("open file: too many levels of symbolic links"), + fmt.Errorf(symlinkExists), }, { "exists: fifo", diff --git a/cmd/safcm-remote/build.sh b/cmd/safcm-remote/build.sh index 4f102c9..7edd090 100755 --- a/cmd/safcm-remote/build.sh +++ b/cmd/safcm-remote/build.sh @@ -34,6 +34,7 @@ dest=../../remote/helpers mkdir -p "$dest" +build freebsd amd64 build linux amd64 build_arm linux arm 7 # TODO: support more operating systems and architectures diff --git a/cmd/safcm-remote/sync/files.go b/cmd/safcm-remote/sync/files.go index 3e3c7ec..f388606 100644 --- a/cmd/safcm-remote/sync/files.go +++ b/cmd/safcm-remote/sync/files.go @@ -113,7 +113,7 @@ reopen: oldFh, err := OpenFileNoFollow(file.Path) if err != nil { err := err.(*fs.PathError) - if err.Err == syscall.ELOOP { + if err.Err == syscall.ELOOP || err.Err == syscall.EMLINK { // Check if ELOOP was caused not by O_NOFOLLOW but by // too many nested symlinks before the final path // component. @@ -149,6 +149,15 @@ reopen: if !change.Created { // Compare permissions change.Old.Mode = oldStat.Mode() + if change.Old.Mode.Type() == fs.ModeSymlink { + // Some BSD systems permit changing permissions of + // symlinks but ignore them on traversal. To keep it + // simple we don't support that and always use 0777 for + // symlink permissions (the value on GNU/Linux). + // + // TODO: Add proper support for symlinks on BSD + change.Old.Mode |= 0777 + } if change.Old.Mode != file.Mode { if change.Old.Mode.Type() != file.Mode.Type() { changeType = true diff --git a/cmd/safcm-remote/sync/files_test.go b/cmd/safcm-remote/sync/files_test.go index e7864ba..91a85be 100644 --- a/cmd/safcm-remote/sync/files_test.go +++ b/cmd/safcm-remote/sync/files_test.go @@ -261,43 +261,38 @@ func TestSyncFiles(t *testing.T) { // don't want to modify the running system. Use this // test (and the one below for triggers) as a basic // check that absolute paths work. + // + // Use numeric IDs as not all systems use root/root; + // for example BSDs use root/wheel. "absolute paths: no change", safcm.MsgSyncReq{ Files: map[string]*safcm.File{ "/": { Path: "/", Mode: fs.ModeDir | 0755, - User: "root", - Uid: -1, - Group: "root", - Gid: -1, + Uid: 0, + Gid: 0, OrigGroup: "group", }, "/etc": { Path: "/etc", Mode: fs.ModeDir | 0755, - User: "root", - Uid: -1, - Group: "root", - Gid: -1, + Uid: 0, + Gid: 0, OrigGroup: "group", }, "/tmp": { Path: "/tmp", Mode: fs.ModeDir | 0777 | fs.ModeSticky, - User: "root", - Uid: -1, - Group: "root", - Gid: -1, + Uid: 0, + Gid: 0, OrigGroup: "group", }, "/var/tmp": { Path: "/var/tmp", Mode: fs.ModeDir | 0777 | fs.ModeSticky, - User: "root", - Uid: -1, - Group: "root", - Gid: -1, + Uid: 0, + Gid: 0, OrigGroup: "group", }, }, @@ -756,10 +751,8 @@ func TestSyncFiles(t *testing.T) { "/": { Path: "/", Mode: fs.ModeDir | 0755, - User: "root", - Uid: -1, - Group: "root", - Gid: -1, + Uid: 0, + Gid: 0, OrigGroup: "group", TriggerCommands: []string{ "echo trigger /", @@ -768,10 +761,8 @@ func TestSyncFiles(t *testing.T) { "/tmp": { Path: "/tmp", Mode: fs.ModeDir | 0777 | fs.ModeSticky, - User: "root", - Uid: -1, - Group: "root", - Gid: -1, + Uid: 0, + Gid: 0, OrigGroup: "group", TriggerCommands: []string{ "echo trigger /tmp", diff --git a/cmd/safcm-remote/sync/filetest/filetest.go b/cmd/safcm-remote/sync/filetest/filetest.go index b6fcb7c..6a594e6 100644 --- a/cmd/safcm-remote/sync/filetest/filetest.go +++ b/cmd/safcm-remote/sync/filetest/filetest.go @@ -63,6 +63,7 @@ func WalkDir(basePath string) ([]File, error) { return err } f.Data = []byte(x) + f.Mode |= 0777 // see sync/files.go } res = append(res, f) return nil diff --git a/cmd/safcm/config/files.go b/cmd/safcm/config/files.go index 8d1a728..8f63770 100644 --- a/cmd/safcm/config/files.go +++ b/cmd/safcm/config/files.go @@ -79,6 +79,7 @@ via "safcm fixperms". return err } data = []byte(x) + perm |= 0777 // see cmd/safcm-remote/sync/files.go } else { return fmt.Errorf("%q: file type not supported", path) } diff --git a/cmd/safcm/config/files_test.go b/cmd/safcm/config/files_test.go index 9f226cd..46da590 100644 --- a/cmd/safcm/config/files_test.go +++ b/cmd/safcm/config/files_test.go @@ -19,6 +19,7 @@ import ( "fmt" "io/fs" "os" + "runtime" "syscall" "testing" @@ -45,6 +46,9 @@ func TestLoadFiles(t *testing.T) { t.Fatal(err) } + // Regular users cannot create sticky files + skipInvalidSticky := runtime.GOOS == "freebsd" + chmod("files-invalid-perm-dir/files", 0500) defer chmod("files-invalid-perm-dir/files", 0700) chmod("files-invalid-perm-dir/files/etc/", 0755) @@ -58,9 +62,11 @@ func TestLoadFiles(t *testing.T) { chmod("files-invalid-perm-file-executable/files", 0755) chmod("files-invalid-perm-file-executable/files/etc", 0755) chmod("files-invalid-perm-file-executable/files/etc/rc.local", 0750) - chmod("files-invalid-perm-file-sticky/files", 0755) - chmod("files-invalid-perm-file-sticky/files/etc", 0755) - chmod("files-invalid-perm-file-sticky/files/etc/resolv.conf", 01644) + if !skipInvalidSticky { + chmod("files-invalid-perm-file-sticky/files", 0755) + chmod("files-invalid-perm-file-sticky/files/etc", 0755) + chmod("files-invalid-perm-file-sticky/files/etc/resolv.conf", 01644) + } err = syscall.Mkfifo("files-invalid-type/files/invalid", 0644) if err != nil { @@ -79,18 +85,21 @@ via "safcm fixperms". tests := []struct { group string + skip bool exp map[string]*safcm.File expErr error }{ { "empty", + false, nil, nil, }, { "group", + false, map[string]*safcm.File{ "/": { Path: "/", @@ -169,31 +178,37 @@ host3.example.net { "files-invalid-type", + false, nil, fmt.Errorf("files-invalid-type: \"files-invalid-type/files/invalid\": file type not supported"), }, { "files-invalid-perm-dir", + false, nil, fmt.Errorf("files-invalid-perm-dir: \"files-invalid-perm-dir/files\": invalid permissions 0500" + errMsg), }, { "files-invalid-perm-dir-setgid", + false, nil, fmt.Errorf("files-invalid-perm-dir-setgid: \"files-invalid-perm-dir-setgid/files/etc\": invalid permissions 02755" + errMsg), }, { "files-invalid-perm-file", + false, nil, fmt.Errorf("files-invalid-perm-file: \"files-invalid-perm-file/files/etc/resolv.conf\": invalid permissions 0600" + errMsg), }, { "files-invalid-perm-file-executable", + false, nil, fmt.Errorf("files-invalid-perm-file-executable: \"files-invalid-perm-file-executable/files/etc/rc.local\": invalid permissions 0750" + errMsg), }, { "files-invalid-perm-file-sticky", + skipInvalidSticky, nil, fmt.Errorf("files-invalid-perm-file-sticky: \"files-invalid-perm-file-sticky/files/etc/resolv.conf\": invalid permissions 01644" + errMsg), }, @@ -201,6 +216,10 @@ host3.example.net for _, tc := range tests { t.Run(tc.group, func(t *testing.T) { + if tc.skip { + t.SkipNow() + } + res, err := LoadFiles(tc.group) testutil.AssertEqual(t, "res", res, tc.exp) testutil.AssertErrorEqual(t, "err", err, tc.expErr) diff --git a/rpc/dial.go b/rpc/dial.go index 680cb87..f300370 100644 --- a/rpc/dial.go +++ b/rpc/dial.go @@ -112,6 +112,17 @@ compat_stat() { compat_sha512sum() { sha512sum "$1" } +` + case "freebsd": + compat = ` +dir_stat='41777 0 0' +file_stat="100700 $(id -u) $(id -g)" +compat_stat() { + stat -f '%p %u %g' "$1" +} +compat_sha512sum() { + sha512 -q "$1" +} ` default: return fmt.Errorf("internal error: no support for %q", goos) @@ -278,6 +289,8 @@ func connGetGoos(stdin io.Writer, stdout *bufio.Reader) (string, error) { switch x { case "GNU/Linux": goos = "linux" + case "FreeBSD": + goos = "freebsd" default: return "", fmt.Errorf("unsupported OS %q (`uname -o`)", x) } @@ -298,7 +311,7 @@ func connGetGoarch(stdin io.Writer, stdout *bufio.Reader) (string, error) { // NOTE: Adapt cmd/safcm-remote/build.sh when adding new architectures var goarch string switch x { - case "x86_64": + case "x86_64", "amd64": goarch = "amd64" case "armv7l": goarch = "armv7l" -- 2.44.1