diff mbox series

systemd: Ensure that statdpath exists using systemd-tmpfiles

Message ID 20230713102531.131072-1-berto@igalia.com (mailing list archive)
State New, archived
Headers show
Series systemd: Ensure that statdpath exists using systemd-tmpfiles | expand

Commit Message

Alberto Garcia July 13, 2023, 10:25 a.m. UTC
The NFS utils store their state under /var/lib/nfs and they can
generally handle the case where that directory is missing by creating
the appropriate files and directories automatically.

This is not the case of rpc-statd: if sm and sm.bak (under $statdpath,
which also defaults to /var/lib/nfs) are missing the daemon will
refuse to start and will exit with an error.

If nfs-utils is configured with systemd support it can take advantage
of systemd-tmpfiles to ensure that the state directories are always
present and have the appropriate ownership.

This would normally be handled with the StateDirectory directive in
rpc-statd.service, however that method would not be able to change the
ownership of the directories to $statduser because this daemon needs
to be run as root, and only later changes its uid and gid.

Signed-off-by: Alberto Garcia <berto@igalia.com>
---
 configure.ac              | 1 +
 systemd/Makefile.am       | 5 +++++
 systemd/nfs-utils.conf.in | 4 ++++
 3 files changed, 10 insertions(+)
 create mode 100644 systemd/nfs-utils.conf.in

Comments

Steve Dickson July 15, 2023, 8:53 p.m. UTC | #1
Hello,

On 7/13/23 6:25 AM, Alberto Garcia wrote:
> The NFS utils store their state under /var/lib/nfs and they can
> generally handle the case where that directory is missing by creating
> the appropriate files and directories automatically.
> 
> This is not the case of rpc-statd: if sm and sm.bak (under $statdpath,
> which also defaults to /var/lib/nfs) are missing the daemon will
> refuse to start and will exit with an error.
Why are they would be missing? They are created on the
nfs-utils installation.

> 
> If nfs-utils is configured with systemd support it can take advantage
> of systemd-tmpfiles to ensure that the state directories are always
> present and have the appropriate ownership.
> 
> This would normally be handled with the StateDirectory directive in
> rpc-statd.service, however that method would not be able to change the
> ownership of the directories to $statduser because this daemon needs
> to be run as root, and only later changes its uid and gid.
Just curious... how did you test this patch? When I apply it
I get this error

Failed to insert: creating /var/lib/nfs/statd/sm/<client>: Permission denied
STAT_FAIL to <server> for SM_MON of <server_ip>

Maybe this is packing issue but I'm thinking it is more
of systemd issue... the permissions on the sm directory
are
283 drwx------. 2 nobody rpcuser 6 Apr 18 20:00 /var/lib/nfs/statd/sm
instead of
283 drwx------. 2 rpcuser rpcuser 6 Apr 18 20:00 /var/lib/nfs/statd/sm

Even when I change the owner to rpcuser, I still get the
permission error...

steved.
> 
> Signed-off-by: Alberto Garcia <berto@igalia.com>
> ---
>   configure.ac              | 1 +
>   systemd/Makefile.am       | 5 +++++
>   systemd/nfs-utils.conf.in | 4 ++++
>   3 files changed, 10 insertions(+)
>   create mode 100644 systemd/nfs-utils.conf.in
> 
> diff --git a/configure.ac b/configure.ac
> index 6fbcb974..fe958ab3 100644
> --- a/configure.ac
> +++ b/configure.ac
> @@ -695,6 +695,7 @@ AC_CONFIG_COMMANDS_PRE([eval eval _rpc_pipefsmount=$rpc_pipefsmount])
>   
>   AC_CONFIG_FILES([
>   	Makefile
> +	systemd/nfs-utils.conf
>   	systemd/rpc-gssd.service
>   	systemd/rpc_pipefs.target
>   	systemd/var-lib-nfs-rpc_pipefs.mount
> diff --git a/systemd/Makefile.am b/systemd/Makefile.am
> index b4483222..6127986e 100644
> --- a/systemd/Makefile.am
> +++ b/systemd/Makefile.am
> @@ -5,6 +5,9 @@ MAINTAINERCLEANFILES = Makefile.in
>   udev_rulesdir = /usr/lib/udev/rules.d/
>   udev_files = 60-nfs.rules
>   
> +sdtmpfilesdir = /usr/lib/tmpfiles.d/
> +sdtmpfiles_files = nfs-utils.conf
> +
>   unit_files =  \
>       nfs-client.target \
>       rpc_pipefs.target \
> @@ -85,4 +88,6 @@ install-data-hook: $(unit_files) $(udev_files)
>   	cp $(rpc_pipefs_mount_file) $(DESTDIR)/$(unitdir)/$(rpc_pipefsmount)
>   	mkdir -p $(DESTDIR)/$(udev_rulesdir)
>   	cp $(udev_files) $(DESTDIR)/$(udev_rulesdir)
> +	mkdir -p $(DESTDIR)/$(sdtmpfilesdir)
> +	cp $(sdtmpfiles_files) $(DESTDIR)/$(sdtmpfilesdir)
>   endif
> diff --git a/systemd/nfs-utils.conf.in b/systemd/nfs-utils.conf.in
> new file mode 100644
> index 00000000..a44c337e
> --- /dev/null
> +++ b/systemd/nfs-utils.conf.in
> @@ -0,0 +1,4 @@
> +# This is a systemd-tmpfiles configuration file
> +# type path			mode	uid	gid	age	argument
> +d      @statdpath@/sm		0700	@statduser@	:root	-       -
> +d      @statdpath@/sm.bak	0700	@statduser@	:root	-	-
Alberto Garcia July 18, 2023, 10:16 p.m. UTC | #2
On Sat, Jul 15, 2023 at 04:53:02PM -0400, Steve Dickson wrote:
> > This is not the case of rpc-statd: if sm and sm.bak (under
> > $statdpath, which also defaults to /var/lib/nfs) are missing the
> > daemon will refuse to start and will exit with an error.
> Why are they would be missing? They are created on the nfs-utils
> installation.

Hello,

yes, in a traditional Linux system that is indeed the case. The idea
behind this is to add support to factory reset and stateless scenarios
like the ones described here:

   https://0pointer.net/blog/projects/stateless.html

The goal is that a system can boot with an empty /var and
all necessary files and directories are created without user
intervention. In the case of nfs-utils this is already happening
except for rpc-statd.

For projects that use systemd this is generally easy to do without
touching the code because systemd provides directives that can be used
to ensure that /var/lib/foo, /var/log/foo, etc. exist before a service
is started.

In the rpc-statd case this would normally be as simple as adding
something like "StateDirectory=nfs/sm nfs/sm.bak" to the .service
file. However it seems that this one is a bit special because it goes
like this if I'm not mistaken:

1. The configure script determines $statduser (the value of
   --with-statduser, else rpcuser if available, else nobody).

2. 'make install' creates sm / sm.bak followed by chown $statduser

3. rpc.statd starts as root, then does lstat("/var/lib/nfs/sm", &st)
   and finally setgid(st.st_gid) / setuid(st.st_uid). At this point
   uid/gid is not necessarily what was set during configure/make
   install ($statduser/root) because downstreams can create a
   different user/group and change the ownership of those directories.

StateDirectory and similar directives from systemd can only create
directories owned by the user that starts the service, but since here
the service needs to run as root this would not work.

systemd-tmpfiles can be used for cases like this one, and that's why I
chose it for this patch.

> Just curious... how did you test this patch? When I apply it
> I get this error
> 
> Failed to insert: creating /var/lib/nfs/statd/sm/<client>: Permission denied
> STAT_FAIL to <server> for SM_MON of <server_ip>
> 
> Maybe this is packing issue but I'm thinking it is more
> of systemd issue... the permissions on the sm directory
> are
> 283 drwx------. 2 nobody rpcuser 6 Apr 18 20:00 /var/lib/nfs/statd/sm
> instead of
> 283 drwx------. 2 rpcuser rpcuser 6 Apr 18 20:00 /var/lib/nfs/statd/sm

Are you creating a package with the patched sources? If it's something
like the Fedora one then I think that the problem is that since the
configure script does not use --with-statduser then there's a mismatch
between the user that appears in nfs-utils.conf (added by this patch)
and these lines from the .spec file:

%dir %attr(700,rpcuser,rpcuser) %{_sharedstatedir}/nfs/statd
%dir %attr(700,rpcuser,rpcuser) %{_sharedstatedir}/nfs/statd/sm
%dir %attr(700,rpcuser,rpcuser) %{_sharedstatedir}/nfs/statd/sm.bak

So probably /var/lib/nfs/statd/sm is drwx------ nobody but
            /var/lib/nfs/statd    is drwx------ rpcuser ?

Passing --with-statduser=rpcuser to configure should fix this problem.

After having a look at a couple of downstream packages it seems that
they simply don't use --with-statduser at all and change the ownership
to whatever user/group they want in their post-installation scripts.
So they would need to start doing it if this patch is included in
nfs-utils.

I realize that although this should be trivial to handle by downstream
packagers it does require manual intervention so I'm not expecting it
to be completely uncontroversial. But if you like the overall idea I'm
happy to discuss / iterate this patch further. This can of course be
applied only by the downstreams who are interested in this feature,
but since nfs-utils already uses systemd and the change is rather
small I thought it made more sense to have it directly upstream.

Regards,

Berto
Steve Dickson July 25, 2023, 3:58 p.m. UTC | #3
Sorry for the delay... I'll take another look

steved.

On 7/18/23 6:16 PM, Alberto Garcia wrote:
> On Sat, Jul 15, 2023 at 04:53:02PM -0400, Steve Dickson wrote:
>>> This is not the case of rpc-statd: if sm and sm.bak (under
>>> $statdpath, which also defaults to /var/lib/nfs) are missing the
>>> daemon will refuse to start and will exit with an error.
>> Why are they would be missing? They are created on the nfs-utils
>> installation.
> 
> Hello,
> 
> yes, in a traditional Linux system that is indeed the case. The idea
> behind this is to add support to factory reset and stateless scenarios
> like the ones described here:
> 
>     https://0pointer.net/blog/projects/stateless.html
> 
> The goal is that a system can boot with an empty /var and
> all necessary files and directories are created without user
> intervention. In the case of nfs-utils this is already happening
> except for rpc-statd.
> 
> For projects that use systemd this is generally easy to do without
> touching the code because systemd provides directives that can be used
> to ensure that /var/lib/foo, /var/log/foo, etc. exist before a service
> is started.
> 
> In the rpc-statd case this would normally be as simple as adding
> something like "StateDirectory=nfs/sm nfs/sm.bak" to the .service
> file. However it seems that this one is a bit special because it goes
> like this if I'm not mistaken:
> 
> 1. The configure script determines $statduser (the value of
>     --with-statduser, else rpcuser if available, else nobody).
> 
> 2. 'make install' creates sm / sm.bak followed by chown $statduser
> 
> 3. rpc.statd starts as root, then does lstat("/var/lib/nfs/sm", &st)
>     and finally setgid(st.st_gid) / setuid(st.st_uid). At this point
>     uid/gid is not necessarily what was set during configure/make
>     install ($statduser/root) because downstreams can create a
>     different user/group and change the ownership of those directories.
> 
> StateDirectory and similar directives from systemd can only create
> directories owned by the user that starts the service, but since here
> the service needs to run as root this would not work.
> 
> systemd-tmpfiles can be used for cases like this one, and that's why I
> chose it for this patch.
> 
>> Just curious... how did you test this patch? When I apply it
>> I get this error
>>
>> Failed to insert: creating /var/lib/nfs/statd/sm/<client>: Permission denied
>> STAT_FAIL to <server> for SM_MON of <server_ip>
>>
>> Maybe this is packing issue but I'm thinking it is more
>> of systemd issue... the permissions on the sm directory
>> are
>> 283 drwx------. 2 nobody rpcuser 6 Apr 18 20:00 /var/lib/nfs/statd/sm
>> instead of
>> 283 drwx------. 2 rpcuser rpcuser 6 Apr 18 20:00 /var/lib/nfs/statd/sm
> 
> Are you creating a package with the patched sources? If it's something
> like the Fedora one then I think that the problem is that since the
> configure script does not use --with-statduser then there's a mismatch
> between the user that appears in nfs-utils.conf (added by this patch)
> and these lines from the .spec file:
> 
> %dir %attr(700,rpcuser,rpcuser) %{_sharedstatedir}/nfs/statd
> %dir %attr(700,rpcuser,rpcuser) %{_sharedstatedir}/nfs/statd/sm
> %dir %attr(700,rpcuser,rpcuser) %{_sharedstatedir}/nfs/statd/sm.bak
> 
> So probably /var/lib/nfs/statd/sm is drwx------ nobody but
>              /var/lib/nfs/statd    is drwx------ rpcuser ?
> 
> Passing --with-statduser=rpcuser to configure should fix this problem.
> 
> After having a look at a couple of downstream packages it seems that
> they simply don't use --with-statduser at all and change the ownership
> to whatever user/group they want in their post-installation scripts.
> So they would need to start doing it if this patch is included in
> nfs-utils.
> 
> I realize that although this should be trivial to handle by downstream
> packagers it does require manual intervention so I'm not expecting it
> to be completely uncontroversial. But if you like the overall idea I'm
> happy to discuss / iterate this patch further. This can of course be
> applied only by the downstreams who are interested in this feature,
> but since nfs-utils already uses systemd and the change is rather
> small I thought it made more sense to have it directly upstream.
> 
> Regards,
> 
> Berto
>
diff mbox series

Patch

diff --git a/configure.ac b/configure.ac
index 6fbcb974..fe958ab3 100644
--- a/configure.ac
+++ b/configure.ac
@@ -695,6 +695,7 @@  AC_CONFIG_COMMANDS_PRE([eval eval _rpc_pipefsmount=$rpc_pipefsmount])
 
 AC_CONFIG_FILES([
 	Makefile
+	systemd/nfs-utils.conf
 	systemd/rpc-gssd.service
 	systemd/rpc_pipefs.target
 	systemd/var-lib-nfs-rpc_pipefs.mount
diff --git a/systemd/Makefile.am b/systemd/Makefile.am
index b4483222..6127986e 100644
--- a/systemd/Makefile.am
+++ b/systemd/Makefile.am
@@ -5,6 +5,9 @@  MAINTAINERCLEANFILES = Makefile.in
 udev_rulesdir = /usr/lib/udev/rules.d/
 udev_files = 60-nfs.rules
 
+sdtmpfilesdir = /usr/lib/tmpfiles.d/
+sdtmpfiles_files = nfs-utils.conf
+
 unit_files =  \
     nfs-client.target \
     rpc_pipefs.target \
@@ -85,4 +88,6 @@  install-data-hook: $(unit_files) $(udev_files)
 	cp $(rpc_pipefs_mount_file) $(DESTDIR)/$(unitdir)/$(rpc_pipefsmount)
 	mkdir -p $(DESTDIR)/$(udev_rulesdir)
 	cp $(udev_files) $(DESTDIR)/$(udev_rulesdir)
+	mkdir -p $(DESTDIR)/$(sdtmpfilesdir)
+	cp $(sdtmpfiles_files) $(DESTDIR)/$(sdtmpfilesdir)
 endif
diff --git a/systemd/nfs-utils.conf.in b/systemd/nfs-utils.conf.in
new file mode 100644
index 00000000..a44c337e
--- /dev/null
+++ b/systemd/nfs-utils.conf.in
@@ -0,0 +1,4 @@ 
+# This is a systemd-tmpfiles configuration file
+# type path			mode	uid	gid	age	argument
+d      @statdpath@/sm		0700	@statduser@	:root	-       -
+d      @statdpath@/sm.bak	0700	@statduser@	:root	-	-