When running a Linux system, you often want to create full-system backups. However, running processes as root is generally bad practice. This article looks at how to run a full-system backup as a non-root user.
Capabilities primer
Linux has a concept of capabilities, which are fine-grained privileges that can be granted to processes and executable files.
The capabilities(7)
man page has a good summary:
For the purpose of performing permission checks, traditional UNIX implementations distinguish two categories of processes: privileged processes (whose effective user ID is 0, referred to as superuser or root), and unprivileged processes (whose effective UID is non‐zero). Privileged processes bypass all kernel permission checks, while unprivileged processes are subject to full permission checking based on the process’s credentials (usually: effective UID, effective GID, and supplementary group list).
Starting with Linux 2.2, Linux divides the privileges traditionally associated with superuser into distinct units, known as capabilities, which can be independently enabled and disabled. Capabilities are a per-thread attribute.
A practical example: ping
The ping(1)
utility is used to send ICMP ECHO packets, commonly known as
pings.
To do this, a raw network socket must be used. Raw sockets can inject arbitrary IP packets directly into the kernel’s network stack, without using the higher-level UDP or TCP interfaces the kernel provides. This can be dangerous, as one could use this to, e.g., impersonate the SSH server on the machine by sending packets with a source port of 22 even though the real SSH server is already using that port.
Because of this danger, using raw sockets requires higher privileges. On many
other UNIX systems (unices?), this means only the root user can create raw
sockets. So on those systems, the /usr/bin/ping
utility has the set-UID bit
set, meaning it runs as the root user no matter which user invoked the program.
This is risky, and is something we want to avoid; if the ping
utility had
a bug, it could be exploited to run arbitrary code as root.
On Linux, capabilities let us grant fine-grained access to privileged resources,
such as raw network sockets. On modern Linux, the /usr/bin/ping
program is not
set-UID, and instead has the CAP_NET_RAW
capability set on it, meaning that no
matter which user runs it, the process will have the ability to create raw
network sockets. This is safer than running as root, as even if the program has
a bug which can be exploited to run arbitrary code, the attacker can only use
the privileges granted by CAP_NET_RAW
, and doesn’t have full access to
privileged resources.
Using capabilities to perform rootless backups
There’s a capability called CAP_DAC_READ_SEARCH
, which effectively grants read
permission on all files and directories, as well as execute/search permission on
all directories. This means that a process with CAP_DAC_READ_SEARCH
can read
any file on the machine.
There are two approaches we can use for this:
- Create a systemd service which runs our backup program with the
CAP_DAC_READ_SEARCH
capability enabled. - Create a new user account for backups, and create a copy of the backup
program which only that user can run, and set
CAP_DAC_READ_SEARCH
on that program.
The first method is a bit more limited; we can only perform the backup by
running the systemd service. The second provides a little more flexibility, as
we can log in as our backup user and run backups manually. Or, in my case, I use
Kopia, which has a convenient kopia snapshot estimate
command that still needs to read all of the files, but doesn’t actually perform
a backup — it just estimates the space/time needed to create one.
The two are also not mutually exclusive. You can do one, the other, both together, or both separately.
Method 1 — systemd service
First, we’ll create a systemd service which runs our backup program. It will
need to be located at /etc/systemd/system/backup.service
.1 You can use the
following command to easily create and edit the file in the right
location:2
# systemctl edit --full --force backup.service
Service specification
In the file, write the following:
|
|
You can reference the systemd.unit(5)
and systemd.service(5)
man page for
reference on the options used here.
For the ExecStart=
setting, you’ll need to provide the command used to run
your backup. It must be an absolute path.
For example, I use /usr/bin/kopia snapshot create /
.
Depending on your backup solution, you may need to provide some type of
configuration or login information, e.g. a password for the location to which
your backups are being uploaded. You can use the Environment=
and/or
EnvironmentFile=
directives to set environment variables. Or if you need more
flexibility, you can write a shell script to configure and perform your backup,
then use that as the ExecStart=
command.
The DynamicUser=
option will create a new user and group to use for the
execution of this service. If you want to use a static pre-existing user
instead, replace this line with User=<username>
.
Timer specification
To run our backup service occasionally, we’ll create a systemd timer unit to trigger it.
Run systemctl edit --full --force backup.timer
to create the timer unit, and
write the following:
|
|
See systemd.timer(5)
for timer unit options, and systemd.time(7)
for the
format of calendar time specifications (for the OnCalendar=
value).
Enabling our units
We can start and enable3 the timer using the following command:
# systemctl enable --now backup.timer
Method 2 — dedicated backup user
Installing necessary tools
For this method, we’ll need some utilities for dealing with capabilities
installed. Specifically, we need setcap(8)
. On Debian, we can install it with
the libcap2-bin
package:2
# apt install libcap2-bin
Creating the user
Next, we’ll create a UNIX user account to be in charge of our backups:
# useradd -m -c 'Backup user' backup
Setting up the specialized executable
Now we’ll give our backup user a copy of the program used to perform backups,
and set the permissions so only they can execute it. For me, the program is
/usr/bin/kopia
, but if you’re using a different software, you’ll have to
adjust the commands appropriately.
[backup]$ mkdir ~/bin
[backup]$ cp /usr/bin/kopia ~/bin/kopia
[backup]$ chmod 0700 ~/bin/kopia
Now, for the important part, we’ll set the CAP_DAC_READ_SEARCH
capability on
the copy of the executable.
# setcap cap_dac_read_search=+ep ~backup/bin/kopia
Warning: This will only work for programs which are native executables. This means that Borg, which is a Python script, won’t work. If you use Borg and want to know how to make this work, send me an email and I’ll add details on how to do it.
Now we can edit the backup
user’s ~/.profile
script to add the ~/bin
directory to the PATH
, so that running kopia
will use ~/bin/kopia
and not
/usr/bin/kopia
. We’ll add the following:
|
|
And now our unprivileged backup
user can perform full-system backups using
kopia
! If desired, this method can be combined with
Method 1 to automatically schedule backups
using a systemd timer.
It just needs to be in this directory and end with
.service
. Thebackup
part of the name is arbitrary, however the service and the timer created later will need to have the same name. ↩︎The prefix character (e.g.,
$
or#
) in front of the command has a meaning. See the Arch Wiki’s explanation. ↩︎ ↩︎Starting the timer means making it active now, and enabling the timer means making it start automatically on boot. ↩︎