10.LAB · Hands-On

Onboard a Team

A single scenario that exercises every command from the reference page — from "the team doesn't exist yet" to "one of them just left the company."

Scenario

You're the new junior administrator at Heliotrope Defense Systems. The CTO walks over to your desk. "Three engineers start Monday. They need shared storage, SSH access, and to be set up properly — not the way the last admin did it. Have them ready by EOD Friday."

By the end of this lab you will have created a group, three users, set aged passwords, built a shared project directory with the right permissions, configured key-based SSH (with root login disabled), mounted a data volume, and — in the bonus round — fully off-boarded a team member who decided to leave on Tuesday.

Setup — before you start

You need a Linux system where you have root access and won't break anything important. Do not do this on your personal workstation or any production machine.

01
Create the team group
Reference Topic 4 · groupadd, getent

The team is called engineering. Create the group first, before you create any users — that way you can add each new user to it at creation time instead of having to come back and modify them afterward.

Do this
root# groupadd engineering
Why this works

groupadd adds a single line to /etc/group with the new group name, a placeholder for the (essentially unused) group password, an auto-assigned GID, and no members yet. The GID will be something like 1001 or 1002 depending on what's already there.

Verify

Confirm the group exists and is empty:

root# getent group engineering engineering:x:1001:

You should see the line with the group name, the placeholder x, the GID, and a trailing colon with no members after it. That trailing colon is normal — it means "no supplementary members yet."

02
Create three users and add them to the group
Reference Topic 1 · useradd

The three engineers are alice, bob, and carol. Each gets a home directory, bash as a login shell, a sensible comment field, and membership in engineering from the start.

Do this
root# useradd -m -s /bin/bash -c "Alice Chen, Engineering" -G engineering alice root# useradd -m -s /bin/bash -c "Bob Marquez, Engineering" -G engineering bob root# useradd -m -s /bin/bash -c "Carol Diaz, Engineering" -G engineering carol
Why this works

-m creates each user's home directory and copies in the skeleton files from /etc/skel. -s /bin/bash gives them an interactive shell. -c populates the GECOS comment field with their name and team. -G engineering adds the supplementary group.

By default, useradd also creates a user-private group with the same name as each user (alice's primary group is alice, bob's is bob, etc.), and engineering becomes a supplementary group for each.

If you'd used -G alone (no -a) We're using -G during creation, which is fine because there are no existing memberships to wipe out. After the account exists, always use usermod -aG to append — never usermod -G on an existing user, or you'll silently wipe out everything they had.
Verify

Inspect each user's groups:

root# id alice uid=1003(alice) gid=1003(alice) groups=1003(alice),1001(engineering) root# getent group engineering engineering:x:1001:alice,bob,carol

All three users appear in the engineering group line, and each has the group listed as supplementary.

03
Set passwords and age them
Reference Topic 2 · passwd, chage

Give each user a temporary password and force them to change it at first login. The policy: maximum 90 days, minimum 1 day, 14 days of warning before expiry.

Do this
# Set a temporary password for each (you'll be prompted twice each) root# passwd alice root# passwd bob root# passwd carol # Force a password change at next login + set aging policy root# for u in alice bob carol; do > chage -d 0 -m 1 -M 90 -W 14 $u > done
Why this works

chage -d 0 sets the "last password change" to epoch day zero, which the system interprets as "the password is expired." At next login (console, SSH, anywhere) the user will be required to change it before doing anything else. The other flags set the rolling policy after they pick a new one.

Verify
root# chage -l alice Last password change : password must be changed Password expires : password must be changed Password inactive : password must be changed Account expires : never Minimum number of days between password change : 1 Maximum number of days between password change : 90 Number of days of warning before password expires : 14

The "must be changed" lines confirm the next login will prompt for a new password.

04
Build the shared project directory
Reference Topic 5 · mkdir, chgrp, chmod, setgid

The team needs a place to put shared work that all three of them can read and write. Put it at /srv/engineering, give it group ownership engineering, and set the setgid bit so files created inside automatically inherit the group.

Do this
# 1. Create the directory tree root# mkdir -pv /srv/engineering/{shared,scripts,docs} # 2. Set the group ownership recursively root# chgrp -R engineering /srv/engineering # 3. Set permissions: rwx for owner, rwx for group, nothing for others # The leading 2 is the SETGID bit. root# chmod -R 2770 /srv/engineering
Why this works

mkdir -p creates the parent /srv/engineering if it doesn't exist, and the brace expansion makes three subdirectories in one call. chgrp -R engineering walks the whole tree, setting group ownership on every entry. The mode 2770 is the important part:

  • 2 — setgid bit on directories; new files inherit the group
  • 7 — rwx for owner
  • 7 — rwx for group
  • 0 — nothing for others (deliberately denying access to anyone not in the group)
Verify

Become alice, create a file in the shared directory, and confirm the group ownership inherited automatically:

root# su - alice alice$ touch /srv/engineering/shared/test.txt alice$ ls -l /srv/engineering/shared/test.txt -rw-rw---- 1 alice engineering 0 Jun 4 11:02 /srv/engineering/shared/test.txt alice$ exit

The group on test.txt is engineering, not alice — that's the setgid bit doing its job. Bob and Carol will also be able to read and write the file because they're in the same group.

05
Restrict SSH to the team, disable root login, enforce keys
Reference Topic 6 · sshd_config, AllowGroups, PermitRootLogin

Three changes to /etc/ssh/sshd_config: only members of engineering can SSH at all, root login is disabled, and password authentication is off (keys only).

Do this
root# cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak-$(date +%F) root# vi /etc/ssh/sshd_config # Add (or change) these three lines: PermitRootLogin no PasswordAuthentication no AllowGroups engineering
Why this works

PermitRootLogin no — the root account cannot log in over SSH at all. Anyone needing root must log in as themselves first, then sudo. Every privileged action is now attributable to a real person.

PasswordAuthentication no — brute-force password guessing is no longer a path in. Only clients with a valid private key can authenticate.

AllowGroups engineering — only members of the engineering group can SSH. Anyone else gets denied immediately, even with valid credentials.

Test FIRST, restart SECOND Always validate the config before restarting the service:
sshd -t
And keep a second SSH session open during the restart, so if you locked yourself out, you still have a way back in to fix it.
Apply
root# sshd -t # exits silently if OK root# systemctl restart ssh # or `sshd` on RHEL
Set up alice's key

Now alice needs a key on the server. From her workstation:

alice@laptop$ ssh-keygen -t ed25519 -C "alice@laptop" alice@laptop$ ssh-copy-id [email protected] # She enters her temporary password ONE more time; ssh-copy-id installs the public # key into ~/.ssh/authorized_keys on the server with the correct permissions.

After this point, alice's next login uses her key (no password prompt), the server still forces her to change her expired password before doing anything else, and then she has normal access.

Verify

From a third machine that is not alice's workstation, try to SSH as alice (you should be rejected — no matching key), and try as root (you should be rejected immediately):

test$ ssh [email protected] Permission denied (publickey). test$ ssh [email protected] Permission denied (publickey).

Both rejected. Now repeat from alice's workstation and she should land at a password-reset prompt.

06
Mount a data volume for the team
Reference Topic 8 · mount, /etc/fstab, blkid

The team's shared directory is going to fill up — they need their own dedicated 500 GB volume. Pretend you've attached a new disk to the VM (in VirtualBox: Settings → Storage → Add Hard Disk; on EC2: Attach Volume). The new device shows up as /dev/sdb. We'll format it, mount it at /srv/engineering, and make the mount persistent.

Skip / adapt this step If you can't easily add a virtual disk, simulate with a loopback file: dd if=/dev/zero of=/var/data.img bs=1M count=512 && losetup -fP /var/data.img. The loop device will appear as /dev/loop0 — substitute that for /dev/sdb1 below.
Do this
# See the new device root# lsblk # Make a single partition on it root# parted -s /dev/sdb mklabel gpt mkpart primary ext4 0% 100% # Format it ext4 root# mkfs.ext4 /dev/sdb1 # Get the UUID (for the fstab entry) root# blkid /dev/sdb1 /dev/sdb1: UUID="c0d1e2f3-4567-89ab-cdef-0123456789ab" TYPE="ext4" # Migrate the existing /srv/engineering contents to a temporary spot root# mv /srv/engineering /srv/engineering.old root# mkdir /srv/engineering # Add the fstab entry (use YOUR UUID from blkid above) root# echo "UUID=c0d1e2f3-4567-89ab-cdef-0123456789ab /srv/engineering ext4 defaults,nosuid,nodev 0 2" >> /etc/fstab # Mount everything in fstab root# mount -a # Restore the contents and reset ownership/perms (the new filesystem doesn't remember them) root# cp -a /srv/engineering.old/. /srv/engineering/ root# chgrp -R engineering /srv/engineering root# chmod -R 2770 /srv/engineering root# rm -rf /srv/engineering.old
Why this works

blkid reports the UUID we'll use in /etc/fstab. Using the UUID instead of /dev/sdb1 means the mount survives the disk being re-enumerated as /dev/sdc after a reboot — UUIDs follow the filesystem, not the device path.

The mount options nosuid,nodev are defense in depth: even if a malicious file lands here somehow, it can't be a setuid-root binary and there can be no device nodes on this partition.

mount -a mounts everything in fstab that isn't already mounted — the same code path that runs at boot. If you got the fstab syntax wrong, now is when it tells you, instead of leaving you with an unbootable system.

Verify
root# df -h /srv/engineering Filesystem Size Used Avail Use% Mounted on /dev/sdb1 492G 152K 467G 1% /srv/engineering root# findmnt /srv/engineering TARGET SOURCE FSTYPE OPTIONS /srv/engineering /dev/sdb1 ext4 rw,nosuid,nodev,relatime

The shared directory now lives on its own 500 GB volume, mounted with security-friendly options, and the mount will reappear on every reboot.

07
Bob leaves. Disable his account properly
Reference Topic 3 · usermod, gpasswd, pkill

Bob has accepted a job elsewhere. His last day is today. You need to disable his account completely — not "lock the password and call it done," but the full close: no password login, no key login, no shell, no running session, no group membership, and the audit trail preserved for thirty days before deletion.

Do this
# 1. Lock the password hash root# usermod -L bob # 2. Expire the account itself (NOT the password) root# usermod -e 1 bob # 3. Change his login shell to nologin — even if SOMETHING bypasses # the previous two, he lands on a "no" instead of a shell. root# usermod -s /usr/sbin/nologin bob # 4. Kick him out of the engineering group root# gpasswd -d bob engineering # 5. Move (don't delete) his authorized_keys root# mv /home/bob/.ssh/authorized_keys /home/bob/.ssh/authorized_keys.offboarded-$(date +%F) # 6. Kill any sessions he has open right now root# pkill -KILL -u bob # 7. Remove his crontab (if he had one) root# crontab -u bob -r 2>/dev/null # non-zero exit if no crontab; that's fine
Why this works

Each step closes a specific door:

  • Step 1 blocks password authentication.
  • Step 2 sets the account to expired — the system treats it as a closed account regardless of which credential is presented.
  • Step 3 means even if a credential succeeds, the user lands on /usr/sbin/nologin, which prints a polite message and exits.
  • Step 4 revokes group access so even if bob came back via a different identity, he doesn't get into the team directory.
  • Step 5 blocks key-based SSH (which doesn't care about the password lock).
  • Step 6 ends any sessions in progress.
  • Step 7 stops scheduled tasks that would otherwise keep running as bob.
Verify
root# passwd -S bob bob L 06/04/2026 1 90 14 -1 (Password locked.) root# chage -l bob | grep Account Account expires : Jan 02, 1970 root# getent passwd bob bob:x:1004:1004:Bob Marquez, Engineering:/home/bob:/usr/sbin/nologin root# getent group engineering engineering:x:1001:alice,carol

Password locked. Account expired in the distant past. Shell set to nologin. Removed from engineering. Bob's home directory is preserved on disk — you'll archive and delete after the 30-day window.

08
Bonus — chroot a contractor to SFTP only
Reference Topic 7 · SSH ChrootDirectory

The team is bringing on a contractor named dave. He needs to drop files into a specific directory using SFTP and nothing else — no shell, no other paths, no SSH tunneling. This is what SSH's ChrootDirectory directive was made for.

Do this
# Create the contractor's group and account root# groupadd contractors root# useradd -m -s /usr/sbin/nologin -G contractors dave root# passwd dave # or set up a key instead # The chroot directory MUST be owned by root and not group-writable root# mkdir -p /srv/contractors/dave/uploads root# chown root:root /srv/contractors /srv/contractors/dave root# chmod 0755 /srv/contractors /srv/contractors/dave root# chown dave:contractors /srv/contractors/dave/uploads root# chmod 0770 /srv/contractors/dave/uploads # Append to /etc/ssh/sshd_config: Match Group contractors ChrootDirectory /srv/contractors/%u ForceCommand internal-sftp AllowTcpForwarding no X11Forwarding no # Also add dave's group to AllowGroups (alongside engineering): AllowGroups engineering contractors root# sshd -t root# systemctl restart ssh
Why this works

The Match Group contractors block applies only to users in that group. Inside the match, ChrootDirectory uses %u as a placeholder for the user's name, so dave is jailed to /srv/contractors/dave. ForceCommand internal-sftp bypasses any shell entirely — the SSH server runs the SFTP subsystem and nothing else.

The strict ownership and permission requirements on the chroot path itself (root:root, not group-writable) are mandatory: sshd refuses to chroot into a directory anyone but root can modify. This is the one footgun of SSH chroot — if dave can write to /srv/contractors/dave, sshd rejects his connection with a confusing error.

Verify

From dave's workstation:

dave$ sftp [email protected] Connected to server.example.com. sftp> pwd Remote working directory: / sftp> ls uploads sftp> cd uploads sftp> put file.zip

Dave sees a tiny filesystem with only his uploads directory in it. He cannot cd /etc — from his point of view /etc doesn't exist. SSH (interactive shell) is rejected because his shell is nologin and the SFTP-only configuration is enforced.

What to turn in

For each of the eight steps, capture and submit:

  1. The command(s) you actually ran (paste from your terminal — not copied from this page).
  2. The verification output showing the step worked.
  3. One sentence in your own words explaining what would have gone wrong if you'd skipped this step.

A clean lab writeup is a deliverable in its own right — the discipline of recording what you did is what separates the engineer who can repeat their work from the one who can't.