Securing circumlunar.space with bind

Found at: zaibatsu.circumlunar.space:70/~solderpunk/phlog/securing-circumlunar-space-with-bind-mounts.txt

Securing circumlunar.space with bind mounts

Lately I have been spending some time preparing for "opening up"
circumlunar.space a bit, by which I mean actively soliciting new users
from the general Gopher-using public.  I will probably announce the
Zaibatsu's existence on the gopher-project mailing list in the near
future.  So, I have been writing scripts to automate the task of
setting up accounts, setting quotas, etc.

The general plan is that people will be offered sftp-only access to
their gopherspace, with authentication via public key only, without
shell access.  Disallowing password authentication means I don't have
to worry about weak passwords getting brute forced.  OpenSSH has very
nice options for enforcing sftp only access.  Modern versions of sshd
have an internally-implemented version of the sftp server, which means
you can chroot users into their home directory without having to
populate it with libraries and things.  I trust the OpenSSH guys to
have got this right, so I feel safe granting this kind of access to
strangers and trusting that they won't be able to get shell access
until I explicitly grant it to them (and I do plan to offer shell
access to anybody who maintains their gopherspace regularly and gives
the impression of being somebody I would like to share a server with).

The one wrinkle on this is Gophernicus' extensive CGI support.  I have
never used Gopher CGI myself and am not really interest in it, but I
know a lot of phloggers use it for various things, so I don't want to
just turn it off.  However, offering sftp-only access rather than
shell access is kind of pointless if somebody can upload any shell
script they like to their gopher hole, point their gopher client at it
and get Gophernicus to execute it.  It's kind of like a very
circuitous shell access of its own.  And, okay, Gophernicus will run
it as a low-privilege user, but I'm still not happy about this loop

At first glance, this is easy to fix.  General CGI executables have to
live in a "cgi_bin" directory inside a user's gopherspace for
Gophernicus to execute them, so when I set up a user account I can
create this directory myself, chown it to root and make sure the user
has no access.  My "make new user" script does this, and this
straightforwardly means people can't upload anything in there.  When
they prove trustworthy and I give them shell access, I can just chown
that directory back to them and we're all good.  CGI problem solved,

Well, not quite.  Because Gophernicus *also* supports executable
gophermaps.  Any file named "gophermap" with the executable bit set
will be treated as a CGI file.  I can't stop people doing this with
simple file permissions, because I *have* to let them be able to
create their own directories in their gopherhole, and as soon as they
create a directory of their own, they can put a gophermap file in it.
This stumped me for a bit.  All I could think was that if each user's
gopherspace was on its own partition, I could mount them with the
noexec option and then CGI would be totally useless for them.  But
giving each user their own partition would be a nightmare, and
besides, the cheap-as-chips OpenVZ VPS technology doesn't give you the
freedom to partition your own dedicated virtual disk.

I did some searching and it turned out I was thinking along the right
lines, but I was just a little bit out of date.  It turns out that
these days, on Linux at least, you don't need to put stuff in its own
partition to facilitate using mount options.  There's this new-fangled
thing called a "bind mount" that I'd never heard of before.  I am
really thrilled that this little project is teaching me new stuff
about system administration.

A bind mount is conceptually pretty simple, it just lets you re-mount
an existing part of your filesystem at a new mountpoint.  Do:

mount -o bind /home/bob /home/alice

and the contents of /home/bob will be mirrored in /home/alice.  It's
kind of like a symbolic link, except that it's more transient (it
won't survive a reboot, unless you put it in /etc/fstab) and a little
more powerful because you can apply all the standard mounting options
like ro, relatime, noexec, nosuid, nodev, etc.  The following:

mount -o bind,ro /home/bob /home/alice

would make /home/alice a read-only mirror of /home/bob (/home/bob
remains writable as always).  Most interestingly, for the current
application, is that you can bind mount some existing location *over
the top of itself*, which basically means that you can apply those
mounting options anywhere you like on your filesystem.  This:

mount -o bind,ro /home/bob /home/bob

simply makes /home/bob read-only, until umount is used to reverse
this.  So, even if your entire system is installed in one big
monolithic "/" partition, as is the style these days, you can still
make any individual directory read-only or no-exec or whatever you
like.  That's pretty nice!

So not only do my scripts now mount new users' gopherspaces noexec
(and remount as such after reach reboot), but I can also do standard
server hardening tricks like mounting /tmp noexec or mounting /usr ro,
despite the fact that at first glance OpenVZ doesn't give me enough
control to facilitate this.