When a new server arrives, someone has to make it ready for the people and programs that will use it. In this project you do that for a small web team: you create their accounts, give them a shared directory that only they can use, run their web site as a service that starts on boot, and let one person restart it without handing them full control of the machine.
It uses the Users and permissions topic most, along with Services with systemd, Installing software, Editing files and Processes.
This project needs a real Linux machine or a virtual machine, because it uses systemd to start the service on boot, and a plain Docker container does not run systemd. Use one of these:
- Windows: Ubuntu in WSL. Recent versions run systemd by default. Check with
ps -p 1 -o comm=, which should printsystemd. - macOS: a Multipass VM. Run
multipass launch --name lab, thenmultipass shell lab. - Linux: your own machine, or a VM on it, if you are happy to add users to it.
The user and directory parts also work in the Docker container from earlier topics, if you want to practise them first.
What you need
A shell on the machine as a user who can run sudo. On Multipass that is the ubuntu user you are logged in as. The commands below start with $ sudo for that reason.
Make sure the packages are up to date and install the two tools the project uses: python3, to serve the web site, and curl, to test it. Both are often installed already, in which case apt says so.
$ sudo apt update
$ sudo apt install -y python3 curl
The brief
- There is a group
webteam. The userssamandpriyaare in it, each with a home directory and Bash as their shell. A third user,alex, exists but is not in the team. Check:id sam,id priyaandid alex. - There is a directory
/srv/webteam, owned byrootand the groupwebteam, that team members can read and write and nobody else can open. New files created inside it belong to thewebteamgroup whoever creates them. Check:ls -ld /srv/webteamshowsdrwxrws---with ownerrootand groupwebteam, and a filesamcreates there has groupwebteam. priyacan read and change a file thatsamcreated in the directory.alexcannot list the directory. Check: try both as each user.- A web site in
/srv/webteam/siteis served on port 8080 by a systemd service calledteam-site, running as a system userwebsitewith no login shell. Check:curl http://127.0.0.1:8080/returns the page, andpsshows the process owned bywebsite. - The service starts on boot. Check:
systemctl is-enabled team-siteprintsenabled, and after a reboot the page is still served without you starting anything. samcan restart the service and see its status withsudo, and cannot run anything else as root. Check:sudo -l -U samlists exactly those two commands, andsudo systemctl stop team-siteassamis refused.
Steps
Create the users and the group
Create the group first, then the users, adding sam and priya to the group as you create them. Ask useradd to make a home directory and to set the shell to /bin/bash; without those options you get no home directory and a basic sh shell.
Give sam a password, because sudo will ask sam for one later.
Hint: useradd -G sets the extra groups a user belongs to. If you forget it, usermod -aG webteam USER adds the group afterwards. Leave out the a and usermod replaces all the user's extra groups instead of adding one.
Show a solution
$ sudo groupadd webteam
$ sudo useradd -m -s /bin/bash -G webteam sam
$ sudo useradd -m -s /bin/bash -G webteam priya
$ sudo useradd -m -s /bin/bash alex
$ sudo passwd sam
New password:
Retype new password:
passwd: password updated successfully
$ id sam
uid=1001(sam) gid=1002(sam) groups=1002(sam),1001(webteam)
$ id priya
uid=1002(priya) gid=1003(priya) groups=1003(priya),1001(webteam)
$ id alex
uid=1003(alex) gid=1004(alex) groups=1004(alex)
$ getent group webteam
webteam:x:1001:sam,priya
Your numbers may differ. Each user also gets a private group with their own name, which is their primary group.
Build the shared directory
Create /srv/webteam with a site directory inside it. Hand both to root as owner and webteam as group, then set the permissions.
Think about what each part of the mode must be:
- The owner,
root, can do anything regardless, so7is fine. - The group needs to list, enter and create files: read, write and execute,
7. - Everyone else should not even see inside:
0. - On top of that, the setgid bit on a directory makes new files and subdirectories inherit the directory's group instead of the creator's primary group. Without it, a file
samcreates would belong to the groupsam, andpriyacould not change it. In octal, setgid is a2in front of the usual three digits.
Hint: in ls -l the setgid bit shows as an s in place of the group's x.
Show a solution
$ sudo mkdir -p /srv/webteam/site
$ sudo chown -R root:webteam /srv/webteam
$ sudo chmod 2770 /srv/webteam /srv/webteam/site
$ ls -ld /srv/webteam /srv/webteam/site
drwxrws--- 3 root webteam 4096 Oct 24 11:23 /srv/webteam
drwxrws--- 2 root webteam 4096 Oct 24 11:23 /srv/webteam/site
Now test it as the users. sudo -iu USER opens a login shell as that user, the same as if they had logged in, and exit brings you back.
$ sudo -iu sam
sam@lab:~$ echo "<h1>Team site</h1>" > /srv/webteam/site/index.html
sam@lab:~$ ls -l /srv/webteam/site
total 4
-rw-rw-r-- 1 sam webteam 19 Oct 24 11:23 index.html
sam@lab:~$ exit
The file belongs to the group webteam, thanks to setgid. It is also group-writable: Ubuntu gives ordinary users a umask of 002 when each has a private group of their own, so new files are rw-rw-r-- rather than rw-r--r--. The r for others does not matter here, because no one outside the group can get into the directory to reach the file.
$ sudo -iu priya
priya@lab:~$ echo "<p>Edited by priya</p>" >> /srv/webteam/site/index.html
priya@lab:~$ cat /srv/webteam/site/index.html
<h1>Team site</h1>
<p>Edited by priya</p>
priya@lab:~$ exit
$ sudo -iu alex
alex@lab:~$ ls /srv/webteam
ls: cannot open directory '/srv/webteam': Permission denied
alex@lab:~$ exit
Group membership is read when a user logs in. If you add a user to a group while they are logged in, they need to log out and in again before it applies.
Run the site as a service
The site needs a program to serve it. Python includes a small web server, python3 -m http.server, which serves the files in a directory. It is fine for a lab; a real site would use a server such as nginx.
Do not run it as root, and not as sam either: if the program were ever broken into, the attacker would get that account's rights. Create a system user for it instead. A system user has a low user ID, no home directory and no login shell, so no one can log in as it. Add it to webteam so it can read the site.
Then write a unit file in /etc/systemd/system/team-site.service. It needs three sections:
[Unit]with a description, and an instruction to start after the network is up.[Service]with the user and group to run as, the full command to run, and a rule to restart the program if it fails.[Install]saying which target pulls the service in at boot. For a server, that ismulti-user.target.
Bind the server to 127.0.0.1 so only this machine can reach it. After writing the file, tell systemd to reload its configuration, then enable and start the service in one command.
Hint: systemd does not search your PATH, so give the full path to python3. command -v python3 prints it.
Show a solution
$ sudo useradd --system --no-create-home --shell /usr/sbin/nologin -G webteam website
$ id website
uid=999(website) gid=999(website) groups=999(website),1001(webteam)
$ command -v python3
/usr/bin/python3
$ sudo nano /etc/systemd/system/team-site.service
[Unit]
Description=Team web site
After=network.target
[Service]
User=website
Group=webteam
ExecStart=/usr/bin/python3 -m http.server 8080 --bind 127.0.0.1 --directory /srv/webteam/site
Restart=on-failure
[Install]
WantedBy=multi-user.target
$ sudo systemd-analyze verify /etc/systemd/system/team-site.service
$ sudo systemctl daemon-reload
$ sudo systemctl enable --now team-site
Created symlink /etc/systemd/system/multi-user.target.wants/team-site.service → /etc/systemd/system/team-site.service.
$ curl http://127.0.0.1:8080/
<h1>Team site</h1>
<p>Edited by priya</p>
systemd-analyze verify prints nothing when the unit file is valid. enable creates the link that makes the service start at boot, and --now also starts it straight away.
Group=webteam makes webteam the process's main group. That is enough to read the site, so adding website to the group with -G is not strictly needed for the service. It does make the intent clear when someone runs id website later.
If curl fails, read what the service said with sudo journalctl -u team-site.
Give sam a narrow sudo rule
sam looks after the site and needs to restart it after changing the configuration. Adding sam to the sudo group would allow that, but it would also allow everything else. Write a rule that allows only systemctl restart team-site and systemctl status team-site.
Put the rule in its own file in /etc/sudoers.d/ and always edit it with visudo. visudo checks the syntax before saving. A broken sudoers file can lock everyone out of sudo, and on a server without another way in, that is hard to fix.
A sudoers rule reads: who, on which hosts, as which user, which commands. Commands need their full paths.
Show a solution
$ sudo visudo -f /etc/sudoers.d/sam
Add this single line, then save and exit:
sam ALL=(root) /usr/bin/systemctl restart team-site, /usr/bin/systemctl status team-site
Check the file and list what sam may run:
$ sudo visudo -cf /etc/sudoers.d/sam
/etc/sudoers.d/sam: parsed OK
$ sudo -l -U sam
...
User sam may run the following commands on lab:
(root) /usr/bin/systemctl restart team-site, /usr/bin/systemctl status team-site
Now try it as sam. sudo asks for sam's own password, not root's:
$ sudo -iu sam
sam@lab:~$ sudo systemctl restart team-site
[sudo] password for sam:
sam@lab:~$ sudo systemctl stop team-site
Sorry, user sam is not allowed to execute '/usr/bin/systemctl stop team-site' as root on lab.
sam@lab:~$ sudo apt update
Sorry, user sam is not allowed to execute '/usr/bin/apt update' as root on lab.
sam@lab:~$ exit
The rule matches the whole command line, so restart team-site is allowed but restart of any other service is not.
Prove it survives a reboot
A service that only runs because you started it by hand will be gone after the next reboot, often at the worst moment. Restart the machine and check that the site comes back without you doing anything.
Show a solution
On Multipass, reboot from inside the VM, wait a few seconds, and open a new shell:
$ sudo reboot
multipass shell lab
On WSL, close the Ubuntu window, run wsl --shutdown in PowerShell, and open Ubuntu again.
Then:
$ systemctl is-active team-site
active
$ curl http://127.0.0.1:8080/
<h1>Team site</h1>
<p>Edited by priya</p>
Check it works
Users and group:
$ getent group webteam
webteam:x:1001:sam,priya,website
$ id -nG alex
alex
The shared directory, with setgid and no access for others:
$ ls -ld /srv/webteam
drwxrws--- 3 root webteam 4096 Oct 24 11:23 /srv/webteam
$ stat -c '%a %U %G' /srv/webteam
2770 root webteam
$ sudo ls -l /srv/webteam/site
total 4
-rw-rw-r-- 1 sam webteam 42 Oct 24 11:24 index.html
$ sudo -u alex ls /srv/webteam
ls: cannot open directory '/srv/webteam': Permission denied
The service is enabled, running as website, and serving the page:
$ systemctl is-enabled team-site
enabled
$ systemctl is-active team-site
active
$ ps -o user=,cmd= -C python3
website /usr/bin/python3 -m http.server 8080 --bind 127.0.0.1 --directory /srv/webteam/site
$ curl -s http://127.0.0.1:8080/
<h1>Team site</h1>
<p>Edited by priya</p>
The website user cannot log in:
$ getent passwd website
website:x:999:999::/home/website:/usr/sbin/nologin
The home directory is listed but does not exist, which is what --no-create-home means. The user ID may differ on your machine; system users get IDs below 1000.
The sudo rule is narrow:
$ sudo -l -U sam | tail -n 2
User sam may run the following commands on lab:
(root) /usr/bin/systemctl restart team-site, /usr/bin/systemctl status team-site
Going further
- Add a default ACL with
setfacl -d -m g:webteam:rwX /srv/webteamso new files are group-writable whatever the creator's umask. - Replace the Python server with nginx, serving
/srv/webteam/site, and work out which user nginx must run as to read it. - Add
ProtectSystem=strictandNoNewPrivileges=trueto the unit, check it still works, and readsystemd-analyze security team-site.