You have a number of options actually, which you can define in the unit file(s) for your services. They all work on the same principle, by using bind mounts within the namespace systemd sets up for the service. Certain parts of the file-system are either mounted read-only, or rendered completely invisible/inaccessible by mounting an empty directory.
PrivateTmp is arguably the most common.
PrivateTmp sets up a new file system namespace for the executed processes and mounts private
/var/tmp directories inside it that is not shared by processes outside of the namespace.
true, mounts the
/boot directories read-only for processes invoked by this unit.
If set to
/etc directory is mounted read-only, too. If set to "strict" the entire file system hierarchy is mounted read-only, except for the API file system subtrees /dev, /proc and /sys
Takes a boolean argument or "read-only". If
true, the directories
/run/user are made inaccessible and empty for processes invoked by this unit. If set to "
read-only", the three directories are made read-only instead
If true, kernel variables accessible through
/proc/irq will be made read-only.
Paths listed in
ReadOnlyPaths= are accessible for reading only, writing will be refused even if the usual file access controls would permit this.
Paths listed in
InaccessiblePaths= will be made inaccessible for processes inside the namespace (along with everything below them in the file system hierarchy).
There might be more options though.
A more layered access policy would probably require SELinux, and a very thorough understanding of it as well as you probably need to adept the Multi Level Security policies.