aboutsummaryrefslogtreecommitdiff
path: root/rsh/e2e_test.go
diff options
context:
space:
mode:
authorLena <lena@omega>2026-08-16 00:00:00 +0000
committerLena <lena@omega>2026-08-16 00:00:00 +0000
commitbeaa0c970d6c3538119b8a34a3f2f754b45e54cf (patch)
tree0a4be0a63a5c5b40f3db66a7e2b6c603e60d8473 /rsh/e2e_test.go
parent0890d139e5fa501561e469aca7854791a936fcb6 (diff)
downloadrsend-beaa0c970d6c3538119b8a34a3f2f754b45e54cf.tar.gz
native: harden rsync and SSH transport
Update rsync to 3.5.0 and Go to 1.26.6. Bound SSH handshakes, pin host-key types, and build 16 KB-aligned hardened executables.
Diffstat (limited to 'rsh/e2e_test.go')
-rw-r--r--rsh/e2e_test.go64
1 files changed, 54 insertions, 10 deletions
diff --git a/rsh/e2e_test.go b/rsh/e2e_test.go
index 88cd317..e6f82b9 100644
--- a/rsh/e2e_test.go
+++ b/rsh/e2e_test.go
@@ -2,6 +2,7 @@ package main
import (
"bytes"
+ "io"
"os"
"os/exec"
"path/filepath"
@@ -28,26 +29,69 @@ func TestEndToEndRealRsync(t *testing.T) {
}
keyPath, pub := genClientKey(t)
- srv := newTestServer(t, pub, shellExec)
+ remoteRoot := t.TempDir()
+ execute := func(cmd string, stdin io.Reader, stdout, stderr io.Writer) int {
+ return shellExecIn(remoteRoot, cmd, stdin, stdout, stderr)
+ }
+ srv := newTestServer(t, pub, execute)
kh := writeKnownHosts(t, "127.0.0.1", srv.port(), srv.hostKey.PublicKey())
src := t.TempDir()
- dst := t.TempDir()
+ // A normal rsync-over-SSH destination may contain shell punctuation. rsync
+ // escapes the remote command before handing it to rsh; this catches any
+ // transport change that loses those escapes while joining the command.
+ marker := filepath.Join(remoteRoot, "injected")
+ dst := filepath.Join(t.TempDir(), "backup path;literal$dollar'quote$(touch injected)")
mustWrite(t, filepath.Join(src, "a.txt"), "alpha")
mustWrite(t, filepath.Join(src, "sub", "b.bin"), "\x00\x01\x02\x03beta")
mustWrite(t, filepath.Join(src, "sub", "c.txt"), "gamma gamma gamma")
- cmd := exec.Command(rsyncBin, "-a", "-e", rshBin, src+"/", "u@127.0.0.1:"+dst+"/")
- cmd.Env = append(os.Environ(),
- "RSH_KEY="+keyPath,
- "RSH_KNOWN_HOSTS="+kh,
- "RSH_PORT="+strconv.Itoa(srv.port()),
- )
- if out, err := cmd.CombinedOutput(); err != nil {
- t.Fatalf("rsync: %v\n%s", err, out)
+ runRsync := func(mirror bool) {
+ t.Helper()
+ args := []string{
+ "-rt", "--out-format=%o %n", "--partial-dir=.rsend-partial", "--timeout=300",
+ "--no-perms", "--no-owner", "--no-group", "--omit-dir-times", "-e", rshBin,
+ }
+ if mirror {
+ args = append(args, "--delete-after")
+ }
+ args = append(args, src+"/", "u@127.0.0.1:"+dst+"/")
+ cmd := exec.Command(rsyncBin, args...)
+ cmd.Env = append(os.Environ(),
+ "RSH_KEY="+keyPath,
+ "RSH_KNOWN_HOSTS="+kh,
+ "RSH_PORT="+strconv.Itoa(srv.port()),
+ )
+ if out, err := cmd.CombinedOutput(); err != nil {
+ t.Fatalf("rsync: %v\n%s", err, out)
+ }
}
+ runRsync(false)
checkSame(t, src, dst)
+ if _, err := os.Stat(marker); !os.IsNotExist(err) {
+ t.Fatalf("remote path was evaluated by the shell: %s", marker)
+ }
+
+ // An empty but readable source is a valid mirror. The app intentionally
+ // follows rsync here: every ordinary item in the destination is removed.
+ entries, err := os.ReadDir(src)
+ if err != nil {
+ t.Fatal(err)
+ }
+ for _, entry := range entries {
+ if err := os.RemoveAll(filepath.Join(src, entry.Name())); err != nil {
+ t.Fatal(err)
+ }
+ }
+ runRsync(true)
+ entries, err = os.ReadDir(dst)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(entries) != 0 {
+ t.Fatalf("empty mirror left destination entries: %v", entries)
+ }
}
func mustWrite(t *testing.T, path, content string) {