- Notifications
You must be signed in to change notification settings - Fork38
rsync in Go! implements client and server, which can send or receive files (upload, download, all directions supported)
License
gokrazy/rsync
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
This repository contains a native Go rsync implementation: thegokr-rsync
command implements an rsync client and server, which can send or receive files(all directions supported). Daemon mode is supported, meaning you can deploygokr-rsync
behind SSH (anonymous or authorized), as command or daemon, orlistening directly on the network (on port 873/tcp by default).
The following known improvements are not yet implemented:
- Making
gokr-rsync
chroot (and/or Linux mount namespaces when available?)into the destination directory to reduce chances of accidental file systemmanipulation in case of bugs.
This project accepts contributions as time permits to merge them (best effort).
This rsync implementation is very fresh. It was started in 2021 and doesn’t havemany users yet.
With that warning out of the way, the rsync protocol uses MD4 checksums overfile contents, so at least your file contents should never be able to becorrupted.
There is enough other functionality (delta transfers, file metadata, specialfiles like symlinks or devices, directory structures, etc.) in the rsyncprotocol that provides opportunities for bugs to hide.
I recommend you carefully check that your transfers work, and please do reportany issues you run into!
Language | URL | Note | Max Protocol | Server mode? |
---|---|---|---|---|
C | RsyncProject/rsync (formerly WayneD/rsync) | original “tridge” implementation; I foundolder versions easier to study | 31 | ✔ yes |
C | kristapsdz/openrsync | OpenBSD, good docs | 27 | ✔ yes |
Go | gokrazy/rsync | → you are here ← | 27 | ✔ yes 🎉 |
Go | jbreiding/rsync-go | rsync algorithm | ❌ no | |
Go | kaiakz/rsync-os | only client/receiver | 27 | ❌ no |
Go | knight42 | proxy | ❌ no | |
Go | c4milo/gsync | ❌ no | ||
Java | APNIC-net/repositoryd | archived | ✔ yes | |
Java | JohannesBuchner/Jarsync | archived,internet draft RFC “The rsync Network Protocol” | ✔ yes | |
Java | perlundq/yajsync | ✔ yes | ||
C++ | gilbertchen/acrosync-library | commercial | ❌ no | |
Rust | sourcefrog/rsyn | archived, client, “rsyn is rsync with no c” | 27 | ❌ no |
To serve the current directory via rsync onlocalhost:8730
, use:
go install github.com/gokrazy/rsync/cmd/gokr-rsync@latestgokr-rsync --daemon --gokr.listen=localhost:8730 --gokr.modulemap=pwd=$PWD
You can then copy the contents of the current directory with clients such asrsync(1)
:
% rsync -v --archive --port 8730 rsync://localhost/pwd/ quinereceiving file list ... donecreated directory quine./.git/[…].github/workflows/main.ymlLICENSEMakefileREADME.mdcmd/gokr-rsyncd/rsyncd.godoc.gogo.modgo.suminternal/rsyncd/connection.gointernal/rsyncd/rsyncd.gointerop_test.gosent 1,234 bytes received 5,678 bytes 13,824.00 bytes/sectotal size is 666 speedup is 0.10
…oropenrsync(1)
, shown doing adifferential update:
% openrsync -v --archive --port 8730 rsync://localhost/pwd/ quinesocket.c:109: warning: connect refused: ::1, localhostTransfer starting: 369 files.git/index (1.1 KB, 100.0% downloaded)Transfer complete: 5.5 KB sent, 1.2 KB read, 666 B file size
setup | encrypted | authenticated | private files? | privileges | protocol version | config required |
---|---|---|---|---|---|---|
1. rsync daemon protocol (TCP port 873) | ❌ no | ⚠ rsync (insecure) | ❌ only world-readable | ✔ dropped +namespace | ✔ negotiated | config required |
2. anon SSH (daemon) | ✔ yes | ✔ rsync | ❌ only world-readable | ✔ dropped +namespace | ✔ negotiated | config required |
3. SSH (command) | ✔ yes | ✔ SSH | ✔ yes | ⚠ full user | ⚠ assumed | no config |
4. SSH (daemon) | ✔ yes | ✔ SSH (+ rsync) | ✔ yes | ⚠ full user | ✔ negotiated | ~/.config/gokr-rsyncd.toml required |
Regarding protocol version “assumed”: the flags to send over the network arecomputedbefore starting SSH and hence the remote rsync process. You mightneed to specify--protocol=27
explicitly on the client. Once the connection isestablished, both sidesdo negotiate the protocol, though.
Serving rsync daemon protocol on TCP port 873 is only safe where the networklayer ensures trusted communication, e.g. in a local network (LAN), or whenusingTailscale or similar. In untrusted networks,attackers can eavesdrop on file transfers and possibly even modify filecontents.
Prefer setup 2 instead.
Example:
- Server:
gokr-rsync --daemon --gokr.modulemap=module=/srv/rsync-module
- Client:
rsync rsync://webserver/module/path
This setup is well suited for serving world-readable files withoutauthentication.
Example:
- Server:
gokr-rsync --daemon --gokr.modulemap=module=/srv/rsync-module --gokr.anonssh_listen=:22873
- Client:
rsync -e ssh rsync://webserver/module/path
This setup is well suited for interactive one-off transfers or regular backups,and uses SSH for both encryption and authentication.
Note that becausegokr-rsync
is invoked with user privileges (not rootprivileges), it cannot donamespacingand hence retains more privileges. When serving public data, it is generallypreferable to use setup 2 instead.
Note thatrsync(1)
assumes the server process understands all flags that itsends, i.e. is running the same version on client and server, or at least acompatible-enough version. You can either specify--protocol=27
on the client,or use setup 4, which negotiates the protocol version, side-stepping possiblecompatibility gaps between rsync clients andgokr-rsync
.
Example:
- Server will be started via SSH
- Client:
rsync --rsync-path=gokr-rsync webserver:path
This setup is more reliable than setup 3 because the rsync protocol version willbe negotiated between client and server. This setup is slightly inconvenientbecause it requires a config file to be present on the server in~/.config/gokr-rsyncd.toml
.
Note that this mode of operation is only implemented by the original “trigde”rsync, not in openrsync. Apple started shipping openrsync with macOS 15 Sequoia,so you might need to explicitly start /usr/libexec/rsync/rsync.samba on Macs.
Example:
- Server will be started via SSH
- Client:
rsync -e ssh --rsync-path=gokr-rsync rsync://webserver/module/path
In my tests,gokr-rsync
can easily transfer data at > 6 Gbit/s. The currentbottleneck is the MD4 algorithm itself (not sure whether in the “tridge” rsyncclient, or ingokr-rsync
). Implementing support for more recent protocolversions would help here, as these include hash algorithm negotiation with morerecent choices.
- xattrs (including acls) was introduced in rsync protocol 30, so is currentlynot supported.
Supported environments:
- systemd (Linux)
- Docker (Linux)
- privileged Linux
- privileged non-Linux
In all environments, the default instructions will take care that:
- (On Linux only) Only configured rsync modules from the host file system aremountedread-only into a Linux mount namespace for
gokr-rsync
, to guardagainst data modification and data exfiltration. gokr-rsync
is running without privileges, as usernobody
, to limit thescope of what an attacker can do when exploiting a vulnerability.
Known gaps:
gokr-rsync
does not guard against denial of service attacks, i.e. consumingtoo many resources (connections, bandwidth, CPU, …).- See alsoPer-IP rate limiting withiptables.
We provideagokr-rsyncd.socket
andgokr-rsyncd.service
file for systemd. Thesefiles enables most of systemd’s security features. You can check by runningsystemd-analyze security gokr-rsyncd.service
, which should result in anexposure level of “0.2 SAFE” as of systemd 249 (September 2021).
First, configure your server flags by creating a systemd service override file:
systemctl edit gokr-rsyncd.service
In the opened editor, change the file to:
[Service]ExecStart=ExecStart=/usr/bin/gokr-rsync --daemon --gokr.modulemap=pwd=/etc/tmpfiles.d
Close the editor and install the service using:
systemctlenable --now gokr-rsyncd.socket
Additional hardening recommendations:
- Restrict which IP addresses are allowed to connect to your rsync server, for example:
- using iptables or nftables on your host system
- using
gokr-rsync
’s built-in IP allow/deny mechanism (once implemented) - usingsystemd’s
IPAddressDeny
andIPAddressAllow
ingokr-rsyncd.socket
- To reduce the impact of Denial Of Service attacks, you can restrict resourceswith systemd, seeManagingResources.
- To hide system directories not relevant to any rsync module, usesystemd’s
TemporaryFileSystem=
andBindReadOnlyPaths=
directivesas described inUse TemporaryFileSystem to hide files or directories fromsystemdservices. Notethat youmay need to disableProtectSystem=strict
due to abug.
We provideaDockerfile
forgokr-rsyncd
.
docker run \ --read-only \ -p 127.0.0.1:8730:8730 \ -v /etc/tmpfiles.d:/srv/rsync:ro \ stapelberg/gokrazy-rsync:latest \ --gokr.modulemap=pwd=/srv/rsync
Additional hardening recommendations:
- Restrict which IP addresses are allowed to connect to your rsync server, for example:
- using iptables or nftables on your host system
- using
gokr-rsync
’s built-in IP allow/deny mechanism (once implemented)- Be sure to set up Docker such that the remote IPv4 or IPv6 address is available inside the container, seehttps://michael.stapelberg.ch/posts/2018-12-12-docker-ipv6/
When started asroot
on Linux,gokr-rsync
will create aLinux mountnamespace, mount all configuredrsync modules read-only into the namespace, then change into the namespace usingchroot(2)
and drop privileges usingsetuid(2)
.
Tip: you can verify which file system objects the daemon process can see byusingls -l /proc/$(pidof gokr-rsync)/root/
.
Additional hardening recommendations:
- Restrict which IP addresses are allowed to connect to your rsync server, for example:
- using iptables or nftables on your host system
- using
gokr-rsync
’s built-in IP allow/deny mechanism (once implemented)
When started asroot
on non-Linux (e.g. Mac),gokr-rsync
will dropprivileges usingsetuid(2)
.
To prevent accidental misconfiguration,gokr-rsync
refuses to start when itdetects that it has write permission in any configured rsync module.
About
rsync in Go! implements client and server, which can send or receive files (upload, download, all directions supported)