Using Linux Terminal, Ubuntu Desktop and Ubuntu Server labs #5

Merged
jcoffey-dev merged 5 commits from labs/batch-3 into main 2026-10-31 02:05:03 +00:00
195 changed files with 3730 additions and 18 deletions

No files matched your search

+2
View File
@@ -46,6 +46,8 @@ with `set -e`. Read `lib/lab.sh` for the helpers; the ones most labs use:
`10.99.0.2` (name `peer`), linked to the VM's `10.99.0.1`
- `hosts_add LINE`: an `/etc/hosts` line reset takes out again
- `state_set KEY VALUE`, `state_get KEY`: notes for check and reset
- `sshd_restart`: restart sshd after reset puts its settings back, and
check port 22 answers (on Ubuntu, never restart `ssh.service` yourself)
The peer shares the VM's files, users and `/etc`, but has its own
addresses, routes, firewall and ports. It has no route beyond the VM, so
+1 -6
View File
@@ -9,12 +9,7 @@ rm -f /root/hash-me.txt /root/secret.txt /root/secret.txt.gpg
if [ -f "$LAB_STATE/ssh.tar" ]; then
rm -f /etc/ssh/sshd_config.d/*
tar -C /etc -xf "$LAB_STATE/ssh.tar"
if is_rhel; then
systemctl restart sshd
else
systemctl restart ssh.socket 2>/dev/null
systemctl restart ssh.service 2>/dev/null
fi
sshd_restart
fi
# Root's new keys and gpg's files.
for k in /root/.ssh/id_*; do
+1 -6
View File
@@ -16,12 +16,7 @@ for f in /etc/systemd/system/ssh.socket.d/*; do
grep -qxF "${f##*/}" "$LAB_STATE/socket-d" 2>/dev/null || rm -f "$f"
done
systemctl daemon-reload
if is_rhel; then
systemctl restart sshd
else
systemctl restart ssh.socket 2>/dev/null
systemctl restart ssh.service 2>/dev/null
fi
sshd_restart
# The client side.
if [ -f "$LAB_STATE/root-ssh-config" ]; then
@@ -0,0 +1,30 @@
# Using Linux Terminal 1.1: Files and directories
Making a directory tree in one command, copying with hidden files and
dates kept, hard and symbolic links, and removing directories.
## Before you start
The lab creates user `student` (password `Lab-Pass-1`). Work as `student`
(`sudo -iu student`), so `~` is `/home/student`. The lab only checks that
`~/lab` doesn't exist yet; `le-lab reset` removes it.
## Tasks
1. Create `~/lab/app/config`, `~/lab/app/logs` and `~/lab/app/data` in one
command, and copy `/etc/skel` to `~/lab/skel`, keeping its hidden files
and timestamps.
2. In `~/lab`, create `notes.txt` containing the word `hello`, a hard link
`notes.hard` and a symbolic link `notes.link` to it. Then rename
`notes.txt` to `notes.old`, and see what `cat` makes of each link.
3. Remove `~/lab/app/logs` with `rmdir`. Put a file in `~/lab/app/data`,
try `rmdir` on it, then remove that directory too. Only `config` is left
in `~/lab/app`.
## Hints
- `mkdir -p` with a brace list: `{config,logs,data}`.
- `cp -a`; compare `ls -la ~/lab/skel` with `ls -la /etc/skel`.
- `ln`, `ln -s`, `mv`; `ls -li` shows inode numbers and `stat -c %h` the
link count.
- `rmdir` only removes empty directories: "Directory not empty". `rm -r`.
@@ -0,0 +1,29 @@
H=$(state_get home)
L=$H/lab
# skel_kept: every file under /etc/skel is in ~/lab/skel, same type and
# modification time.
skel_kept() {
local f
while read -r f; do
[ "$(stat -c '%F %Y' "/etc/skel/$f")" = "$(stat -c '%F %Y' "$L/skel/$f" 2>/dev/null)" ] || return 1
done < <(cd /etc/skel && find . -mindepth 1)
}
task 1 "~/lab/app/config, and /etc/skel copied to ~/lab/skel with hidden files and dates"
need "$L/app/config is a directory" '[ -d "$L/app/config" ]'
need "$L/skel holds the hidden files, such as .bashrc" '[ -f "$L/skel/.bashrc" ]'
need "every file in ~/lab/skel has the same date as in /etc/skel" skel_kept
task 2 "notes.hard and notes.link made, then notes.txt renamed to notes.old"
need "$L/notes.old contains hello" 'grep -qw hello "$L/notes.old"'
need "$L/notes.txt is gone (renamed)" '[ ! -e "$L/notes.txt" ] && [ ! -L "$L/notes.txt" ]'
need "notes.hard is a hard link to notes.old (same inode)" '[ ! -L "$L/notes.hard" ] && [ "$L/notes.hard" -ef "$L/notes.old" ]'
need "stat -c %h notes.old prints 2" '[ "$(stat -c %h "$L/notes.old")" = 2 ]'
need "notes.link is a symbolic link to notes.txt" '[ -L "$L/notes.link" ] && [ "$(basename "$(readlink "$L/notes.link")")" = notes.txt ]'
need "notes.link is broken now" '[ ! -e "$L/notes.link" ]'
task 3 "logs and data removed: only config left in ~/lab/app"
need "$L/app exists" '[ -d "$L/app" ]'
need "$L/app/logs is gone" '[ ! -e "$L/app/logs" ]'
need "$L/app/data is gone" '[ ! -e "$L/app/data" ]'
need "ls ~/lab/app lists only config" '[ "$(ls -A "$L/app")" = config ]'
@@ -0,0 +1,3 @@
TITLE="1.1 Files and directories"
CHAPTER=/linux-terminal/1-files-and-filesystems/1-1-files-and-directories/
SYSTEMS="debian"
@@ -0,0 +1,3 @@
H=$(state_get home)
[ -n "$H" ] && rm -rf "$H/lab"
true
@@ -0,0 +1,5 @@
user_add student
H=$(getent passwd student | cut -d: -f6)
[ -e "$H/lab" ] && refuse "$H/lab already exists; the tasks create it. Remove it and start the lab again."
state_set home "$H"
true
@@ -0,0 +1,28 @@
# Using Linux Terminal 1.2: Searching, comparing and changing files
grep and regular expressions, find with errors thrown away, sed with a
backup, and diff.
## Before you start
The lab creates user `student` (password `Lab-Pass-1`). Work as `student`
(`sudo -iu student`), so `~` is `/home/student`. The lab removes the files
the tasks create if they exist, and `le-lab reset` removes them again.
## Tasks
1. Write the names of all users whose shell is `/bin/bash` to
`~/bash-users.txt`, one per line, and nothing else.
2. As `student`, find every regular file under `/etc` bigger than 100 KiB
and save the list in `~/big-etc.txt`, with any permission errors thrown
away: none on the screen and none in the file.
3. Copy `/etc/ssh/sshd_config` to `~/sshd.copy`, then use `sed` to change
`#Port 22` to `Port 2222` in the copy, keeping a `.bak` of it. `diff
~/sshd.copy.bak ~/sshd.copy` shows just that one line changed.
## Hints
- `grep '/bin/bash$' /etc/passwd`, then `cut -d: -f1`; compare `wc -l`
with `grep -c`.
- `find /etc -type f -size +100k`, and `2>/dev/null` for the errors.
- `sed -i.bak 's/^#Port 22$/Port 2222/'`; diff shows a `c` change.
@@ -0,0 +1,35 @@
H=$(state_get home)
B=$H/big-etc.txt
norm() { sed 's#//*#/#g; s/[[:space:]]*$//' "$@"; }
# Each listed path is a regular file under /etc bigger than 100 KiB.
all_big() {
local p
while read -r p; do
case $p in /etc/*) ;; *) return 1 ;; esac
[ -n "$(find "$p" -maxdepth 0 -type f -size +100k 2>/dev/null)" ] || return 1
done < <(norm "$B")
}
# Every such file student can find is listed.
none_missing() {
local p
while read -r p; do
norm "$B" | grep -qxF "$p" || return 1
done < <(runuser -u student -- find /etc -type f -size +100k 2>/dev/null | norm)
}
task 1 "~/bash-users.txt lists exactly the users with shell /bin/bash"
need "$H/bash-users.txt exists" '[ -f "$H/bash-users.txt" ]'
need "it holds the names of the users whose shell is /bin/bash, one per line, and nothing else" \
'[ "$(norm "$H/bash-users.txt" | sort)" = "$(awk -F: "\$7 == \"/bin/bash\" {print \$1}" /etc/passwd | sort)" ]'
task 2 "~/big-etc.txt: regular files under /etc over 100 KiB, no errors"
need "$B exists and lists something" '[ -s "$B" ]'
need "it has no Permission denied lines" '! grep -qi "permission denied" "$B"'
need "every line is a regular file under /etc bigger than 100 KiB" all_big
need "it lists every such file student can find" none_missing
task 3 "sed changed #Port 22 to Port 2222 in ~/sshd.copy, with a .bak"
need "$H/sshd.copy.bak is an unchanged copy of /etc/ssh/sshd_config" 'cmp -s "$H/sshd.copy.bak" "$LAB_STATE/sshd_config" || cmp -s "$H/sshd.copy.bak" /etc/ssh/sshd_config'
need "$H/sshd.copy has Port 2222 and no #Port 22" 'grep -qx "Port 2222" "$H/sshd.copy" && ! grep -qx "#Port 22" "$H/sshd.copy"'
need "diff ~/sshd.copy.bak ~/sshd.copy shows one line changed, as a c change" \
'd=$(diff "$H/sshd.copy.bak" "$H/sshd.copy"); [ "$(wc -l <<<"$d")" = 4 ] && head -n1 <<<"$d" | grep -Eq "^[0-9]+c[0-9]+$"'
@@ -0,0 +1,3 @@
TITLE="1.2 Searching, comparing and changing files"
CHAPTER=/linux-terminal/1-files-and-filesystems/1-2-search-compare-modify/
SYSTEMS="debian"
@@ -0,0 +1,3 @@
H=$(state_get home)
[ -n "$H" ] && rm -f "$H/bash-users.txt" "$H/big-etc.txt" "$H/sshd.copy" "$H/sshd.copy.bak"
true
@@ -0,0 +1,8 @@
grep -qx '#Port 22' /etc/ssh/sshd_config ||
refuse "/etc/ssh/sshd_config has no line '#Port 22' for task 3; put it back as Ubuntu ships it and start the lab again."
user_add student
H=$(getent passwd student | cut -d: -f6)
state_set home "$H"
rm -f "$H/bash-users.txt" "$H/big-etc.txt" "$H/sshd.copy" "$H/sshd.copy.bak"
cp /etc/ssh/sshd_config "$LAB_STATE/sshd_config"
true
@@ -0,0 +1,34 @@
# Using Linux Terminal 2.1: Logs and log rotation
Reading `/var/log/auth.log` and the journal, rotating a log of your own
with logrotate, and filtering the journal by priority.
## Before you start
Work as root (`sudo -i`), and write answers to the files in
`/root/lab-answers/`. The lab creates user `alice`, who then runs four
commands with `sudo`, and it logs a few messages tagged `webapp` at
different priorities. It only checks that `/var/log/webapp` doesn't exist
yet; `le-lab reset` removes it and your logrotate file for it.
## Tasks
1. Find the last three commands `alice` ran with `sudo`, from
`/var/log/auth.log` and from the journal, and write them to
`/root/lab-answers/1`, one per line. Both should show the same commands
at the same times.
2. Create `/var/log/webapp/access.log` with some lines in it, and a
logrotate configuration that rotates it weekly, keeps 4 compressed
copies, and creates a new empty log owned by `root:adm` with mode 640.
Force one rotation.
3. Show only error messages (and worse) from the current boot, and write
the `webapp` messages among them to `/root/lab-answers/3`.
## Hints
- `grep 'COMMAND=' /var/log/auth.log`, `journalctl -t sudo`; `tail -n 3`.
Pasting whole log lines into the answer file is fine.
- A file in `/etc/logrotate.d/` with `weekly`, `rotate 4`, `compress` and
`create 0640 root adm`; `logrotate -d` to test it, `logrotate -f` to
force it. `zcat` reads the old copy.
- `journalctl -p err -b`; `-t webapp` narrows it to one tag.
@@ -0,0 +1,25 @@
# Answer lines, lowercase.
ans() { tr 'A-Z' 'a-z' <"/root/lab-answers/$1" 2>/dev/null; }
LR=$(grep -lE '/var/log/webapp/' /etc/logrotate.d/* 2>/dev/null | head -n1)
W=/var/log/webapp
task 1 "alice's last three sudo commands in /root/lab-answers/1"
need "/root/lab-answers/1 has cat /etc/hostname" 'ans 1 | grep -qF "cat /etc/hostname"'
need "/root/lab-answers/1 has du -sh /var/log" 'ans 1 | grep -qF "du -sh /var/log"'
need "/root/lab-answers/1 has tail -n 5 /var/log/dpkg.log" 'ans 1 | grep -qF "tail -n 5 /var/log/dpkg.log"'
need "/root/lab-answers/1 has only the last three (not ls /etc/apt, the one before)" '! ans 1 | grep -qF "ls /etc/apt"'
task 2 "logrotate rotates /var/log/webapp/access.log weekly, 4 compressed, create 640 root adm"
need "a file in /etc/logrotate.d names /var/log/webapp/access.log" '[ -n "$LR" ]'
need "logrotate -d shows it rotated weekly, keeping 4" 'logrotate -d /etc/logrotate.conf 2>&1 | grep -E "^rotating pattern: .*/var/log/webapp/" | grep -q "weekly (4 rotations)"'
need "logrotate -d reports no errors for it" '! logrotate -d "$LR" 2>&1 | grep -qi "^error"'
need "$LR compresses old copies" 'grep -Eq "^[[:space:]]*compress([[:space:]]|$)" "$LR"'
need "$LR creates the new log with 0640 root adm" 'grep -Eq "^[[:space:]]*create[[:space:]]+0?640[[:space:]]+root[[:space:]]+adm([[:space:]]|$)" "$LR"'
need "$W/access.log is a new empty log" '[ -f $W/access.log ] && [ ! -s $W/access.log ]'
need "$W/access.log is -rw-r----- root adm" '[ "$(stat -c "%a %U %G" $W/access.log)" = "640 root adm" ]'
need "the old lines are in access.log.1.gz (or access.log.1)" '{ zcat $W/access.log.1.gz 2>/dev/null || cat $W/access.log.1 2>/dev/null; } | grep -q .'
task 3 "The webapp messages of priority err and worse in /root/lab-answers/3"
need "/root/lab-answers/3 has the error: cannot connect to database" 'ans 3 | grep -qF "cannot connect to database"'
need "/root/lab-answers/3 has the critical message: worker threads stuck" 'ans 3 | grep -qF "worker threads stuck"'
need "/root/lab-answers/3 has nothing below err (no warning, no notice)" '! ans 3 | grep -qE "cache warmed|disk use at"'
@@ -0,0 +1,3 @@
TITLE="2.1 Logs and log rotation"
CHAPTER=/linux-terminal/2-system-resources/2-1-logs-and-log-rotation/
SYSTEMS="debian"
@@ -0,0 +1,12 @@
rm -f /etc/sudoers.d/le-lab-alice
rm -rf /var/log/webapp
for f in /etc/logrotate.d/*; do
[ -f "$f" ] || continue
grep -qxF "${f##*/}" "$LAB_STATE/logrotate-d" 2>/dev/null && continue
grep -q '/var/log/webapp' "$f" && rm -f "$f"
done
sed -i '\#^"/var/log/webapp/#d' /var/lib/logrotate/status 2>/dev/null
if [ -n "$(state_get installed-rsyslog)" ]; then
DEBIAN_FRONTEND=noninteractive apt-get -y -qq purge rsyslog >/dev/null 2>&1
fi
true
@@ -0,0 +1,27 @@
[ -e /var/log/webapp ] && refuse "/var/log/webapp already exists; task 2 creates it. Remove it and start the lab again."
if ! pkg_installed rsyslog; then
pkg_install rsyslog
state_set installed-rsyslog 1
fi
systemctl enable --now --quiet rsyslog
ls /etc/logrotate.d >"$LAB_STATE/logrotate-d"
user_add alice
# Four commands as alice with sudo, a second apart; the last three are the
# answer to task 1.
cat >/etc/sudoers.d/le-lab-alice <<'X'
alice ALL=(root) NOPASSWD: /usr/bin/ls /etc/apt, /usr/bin/cat /etc/hostname, /usr/bin/du -sh /var/log, /usr/bin/tail -n 5 /var/log/dpkg.log
X
chmod 440 /etc/sudoers.d/le-lab-alice
for c in "/usr/bin/ls /etc/apt" "/usr/bin/cat /etc/hostname" "/usr/bin/du -sh /var/log" "/usr/bin/tail -n 5 /var/log/dpkg.log"; do
runuser -u alice -- sudo -n $c >/dev/null 2>&1 || true
sleep 1
done
rm -f /etc/sudoers.d/le-lab-alice
# Messages for task 3, from notice to critical.
logger -t webapp -p user.notice "cache warmed in 840 ms"
logger -t webapp -p user.warning "disk use at 85 percent on /var"
logger -t webapp -p user.err "cannot connect to database at 10.0.0.5:5432"
logger -t webapp -p user.crit "all worker threads stuck, giving up"
true
@@ -0,0 +1,35 @@
# Using Linux Terminal 2.2: Partitions and filesystems
A GPT partition with an ext4 filesystem mounted at boot by UUID, getting a
deleted partition back, and a read-only filesystem check.
## Before you start
Work as root (`sudo -i`), and write answers to the files in
`/root/lab-answers/`. The lab adds a blank 2 GiB spare disk,
`/dev/loop40`, which stays attached across reboots; its first partition is
`/dev/loop40p1` (the guide's `/dev/vdb` and `/dev/vdb1`). It saves
`/etc/fstab` and puts it back on reset, which also removes the disk.
## Tasks
1. Give `/dev/loop40` a GPT partition table with one 1 GiB partition, make
an ext4 filesystem labeled `data` on it, and mount it at `/mnt/data` at
every boot, by UUID. `findmnt --verify` reports no errors or warnings.
2. Save a file `/mnt/data/keep.txt` containing `keep me`. Unmount the
filesystem, write its partition's start sector to
`/root/lab-answers/2`, delete the partition with fdisk, then recreate it
so that `keep.txt` comes back unchanged. Mount it again.
3. Check the filesystem fully while it's unmounted, without letting fsck
change anything, and save fsck's output in `/root/lab-answers/3`.
## Hints
- `fdisk /dev/loop40` (`g`, `n`, `+1G`, `w`) or `parted`; `mkfs.ext4 -L`;
`blkid` for the UUID; `systemctl daemon-reload` after editing fstab, then
`mount -a`.
- `fdisk -l /dev/loop40` shows the start; recreate with the same start and
answer **N** to "remove the signature". fdisk asks that only at a
terminal: fed from a pipe, it keeps the signature without asking.
`blkid` shows the same UUID.
- `fsck -fn /dev/loop40p1 >/root/lab-answers/3 2>&1`.
@@ -0,0 +1,25 @@
D=$(state_get dev)
P=${D}p1
U=$(blkid -o value -s UUID "$P" 2>/dev/null)
# Partition size in bytes.
psize() { blockdev --getsize64 "$P" 2>/dev/null || echo 0; }
task 1 "GPT, one 1 GiB ext4 partition labeled data, mounted at /mnt/data by UUID"
need "$D has a GPT partition table" '[ "$(blkid -o value -s PTTYPE "$D")" = gpt ]'
need "$D has one partition, $P" '[ -b "$P" ] && [ "$(lsblk -nr -o TYPE "$D" | grep -c part)" = 1 ]'
need "$P is 1 GiB" 's=$(psize); [ "$s" -ge $((1024 * 1024 * 1016)) ] && [ "$s" -le $((1024 * 1024 * 1032)) ]'
need "$P holds ext4 labeled data" '[ "$(blkid -o value -s TYPE "$P")" = ext4 ] && [ "$(blkid -o value -s LABEL "$P")" = data ]'
need "/etc/fstab mounts UUID=$U at /mnt/data" '[ -n "$U" ] && awk -v u="UUID=$U" "\$1 == u && \$2 == \"/mnt/data\" && \$3 == \"ext4\"" /etc/fstab | grep -q .'
need "findmnt --verify reports no errors or warnings" 'findmnt --verify 2>&1 | grep -q "no errors or warnings"'
need "/mnt/data is mounted from it now" '[ -n "$U" ] && [ "$(findmnt -n -o UUID /mnt/data)" = "$U" ]'
task 2 "Partition deleted and recreated at the same start; keep.txt is back"
need "/root/lab-answers/2 holds $P's start sector" '[ "$(answer 2 | grep -o "[0-9]\+" | head -n1)" = "$(cat /sys/class/block/${P#/dev/}/start)" ]'
need "/mnt/data is mounted from $P" '[ -n "$U" ] && [ "$(findmnt -n -o UUID /mnt/data)" = "$U" ]'
need "/mnt/data/keep.txt contains keep me" '[ "$(cat /mnt/data/keep.txt)" = "keep me" ]'
task 3 "A full, read-only fsck, its output in /root/lab-answers/3"
need "/root/lab-answers/3 has fsck's output through pass 5" 'grep -q "Pass 5" /root/lab-answers/3'
need "it ends with the file and block counts" 'grep -Eq "[0-9]+/[0-9]+ files.*, [0-9]+/[0-9]+ blocks" /root/lab-answers/3'
need "fsck ran on the unmounted filesystem" '! grep -qi "is mounted" /root/lab-answers/3'
need "fsck changed nothing: no FILE SYSTEM WAS MODIFIED" '! grep -q "MODIFIED" /root/lab-answers/3'
@@ -0,0 +1,3 @@
TITLE="2.2 Partitions and filesystems"
CHAPTER=/linux-terminal/2-system-resources/2-2-partitions-and-filesystems/
SYSTEMS="debian"
@@ -0,0 +1,7 @@
umount /mnt/data 2>/dev/null
if [ -f "$LAB_STATE/fstab" ]; then
cp -a "$LAB_STATE/fstab" /etc/fstab
systemctl daemon-reload
fi
[ -n "$(state_get had-mnt-data)" ] || rmdir /mnt/data 2>/dev/null
true
@@ -0,0 +1,10 @@
if mountpoint -q /mnt/data || [ -n "$(ls -A /mnt/data 2>/dev/null)" ]; then
refuse "/mnt/data is in use; unmount or empty it and start the lab again."
fi
grep -Eq '^[^#]*[[:space:]]/mnt/data[[:space:]]' /etc/fstab &&
refuse "/etc/fstab already mounts something at /mnt/data; remove that line and start the lab again."
cp -a /etc/fstab "$LAB_STATE/fstab"
[ -d /mnt/data ] && state_set had-mnt-data 1
dev=$(disk_add 0 2G)
state_set dev "$dev"
true
+32
View File
@@ -0,0 +1,32 @@
# Using Linux Terminal 2.3: Crontab format and location
A user crontab with a repeating job and a boot job, a system job in
`/etc/cron.d`, and keeping one user away from crontab.
## Before you start
The lab creates users `student` and `alice` (password `Lab-Pass-1`), puts
a harmless script at `/usr/local/bin/cleanup`, and makes sure cron is
running. It saves `/etc/cron.allow` and `/etc/cron.deny` (if they exist)
and puts them back on reset. Task 1 needs a few minutes of waiting and a
reboot.
## Tasks
1. As `student` (`sudo -iu student`), add a cron job that appends the date
to `~/ticks.log` every two minutes, and one that writes `booted` to
`~/boot.log` at every boot. Wait until `~/ticks.log` has a date or two,
then reboot and look at `~/boot.log`.
2. Make a system job that runs `/usr/local/bin/cleanup` as root at 3:15 am
on the first day of every month, in its own file under `/etc/cron.d`.
3. Stop `alice` from using crontab, without touching anyone else: her
`crontab -l` says she isn't allowed, and `student`'s still works.
## Hints
- `crontab -e`, `crontab -l`; `*/2 * * * *` and `@reboot`; `>>` appends,
`>` overwrites.
- Files in `/etc/cron.d` have a user field after the five time fields, and
cron skips files whose names contain a dot.
- `/etc/cron.deny` lists users who may not use crontab; a `cron.allow`
would shut out everyone it doesn't list.
+39
View File
@@ -0,0 +1,39 @@
H=$(state_get home)
CT=$(crontab -l -u student 2>/dev/null | grep -Ev '^[[:space:]]*(#|$)')
# The cron.d file with the cleanup job.
CD=$(grep -lE '^[[:space:]]*15[[:space:]]+0?3[[:space:]]+1[[:space:]]+\*[[:space:]]+\*[[:space:]]+root[[:space:]]+/usr/local/bin/cleanup[[:space:]]*$' /etc/cron.d/* 2>/dev/null | head -n1)
refused() { runuser -u "$1" -- crontab -l 2>&1 | grep -q "not allowed"; }
others_ok() {
local u
refused root && return 1
for u in $(awk -F: '$3 >= 1000 && $3 < 65534 {print $1}' /etc/passwd); do
[ "$u" = alice ] && continue
refused "$u" && return 1
done
return 0
}
boot_log() {
local i
for i in $(seq 15); do
grep -qx booted "$H/boot.log" 2>/dev/null && return 0
sleep 1
done
return 1
}
task 1 "student's crontab: date to ~/ticks.log every two minutes, booted to ~/boot.log at boot"
need "student's crontab has a line that appends date to ~/ticks.log every two minutes" \
'grep -E "^(\*/2|0-59/2|0,2,4,6,8,10,12,14,16,18,20,22,24,26,28,30,32,34,36,38,40,42,44,46,48,50,52,54,56,58)[[:space:]]+\*[[:space:]]+\*[[:space:]]+\*[[:space:]]+\*[[:space:]]" <<<"$CT" | grep "date" | grep -q ">>.*ticks\.log"'
need "student's crontab has an @reboot line that writes booted to ~/boot.log" 'grep -E "^@reboot[[:space:]]" <<<"$CT" | grep -q "boot\.log"'
need "$H/ticks.log has a date in it" 'grep -Eq "20[0-9][0-9]" "$H/ticks.log"'
need "$H/boot.log contains booted (after a reboot)" boot_log
task 2 "/etc/cron.d job: /usr/local/bin/cleanup as root at 3:15 on the 1st"
need "a file in /etc/cron.d holds 15 3 1 * * root /usr/local/bin/cleanup" '[ -n "$CD" ]'
need "its name has no dot in it" '[[ "${CD##*/}" =~ ^[A-Za-z0-9_-]+$ ]]'
need "it's owned by root and not writable by group or others" '[ "$(stat -c %U "$CD")" = root ] && [ -z "$(find "$CD" -perm /022)" ]'
task 3 "alice can't use crontab; everyone else still can"
need "alice's crontab -l says she isn't allowed" 'refused alice'
need "student's crontab -l still works" '! refused student'
need "no other user (or root) is refused" others_ok
+3
View File
@@ -0,0 +1,3 @@
TITLE="2.3 Crontab format and location"
CHAPTER=/linux-terminal/2-system-resources/2-3-crontab/
SYSTEMS="debian"
+17
View File
@@ -0,0 +1,17 @@
crontab -r -u student 2>/dev/null
crontab -r -u alice 2>/dev/null
H=$(state_get home)
[ -n "$H" ] && rm -f "$H/ticks.log" "$H/boot.log"
for f in cron.allow cron.deny; do
if [ -f "$LAB_STATE/$f" ]; then cp -a "$LAB_STATE/$f" /etc/$f; else rm -f /etc/$f; fi
done
for f in /etc/cron.d/*; do
[ -f "$f" ] || continue
grep -qxF "${f##*/}" "$LAB_STATE/cron-d" 2>/dev/null && continue
grep -q /usr/local/bin/cleanup "$f" && rm -f "$f"
done
[ -n "$(state_get made-cleanup)" ] && rm -f /usr/local/bin/cleanup
if [ -n "$(state_get installed-cron)" ]; then
DEBIAN_FRONTEND=noninteractive apt-get -y -qq purge cron >/dev/null 2>&1
fi
true
+25
View File
@@ -0,0 +1,25 @@
if ! pkg_installed cron; then
pkg_install cron
state_set installed-cron 1
fi
systemctl enable --now --quiet cron
user_add student
user_add alice
crontab -r -u student 2>/dev/null || true
for f in cron.allow cron.deny; do
[ -f /etc/$f ] && cp -a /etc/$f "$LAB_STATE/$f"
done
ls /etc/cron.d >"$LAB_STATE/cron-d"
if [ ! -e /usr/local/bin/cleanup ]; then
cat >/usr/local/bin/cleanup <<'X'
#!/bin/sh
# Practice lab: stands in for a monthly cleanup job.
find /tmp -maxdepth 1 -name 'cleanup-test-*' -mtime +30 -delete
X
chmod 755 /usr/local/bin/cleanup
state_set made-cleanup 1
fi
H=$(getent passwd student | cut -d: -f6)
state_set home "$H"
rm -f "$H/ticks.log" "$H/boot.log"
true
@@ -0,0 +1,34 @@
# Using Linux Terminal 2.4: Troubleshooting with logs
Finding why a service failed, spotting a failed SSH login, and filtering
the journal by priority and by source.
## Before you start
Work as root (`sudo -i`), and write answers to the files in
`/root/lab-answers/`. The peer, at `10.99.0.2`, stands in for another
machine: get a shell there with `ip netns exec lepeer bash`, and this VM
is `10.99.0.1` from it. The lab also logs two error messages, one from a
program and one from the kernel. Do task 3 within an hour of starting the
lab (or reset and start again).
## Tasks
1. Create `/etc/systemd/system/broken.service` with
`ExecStart=/usr/local/bin/missing`, run `daemon-reload` and start it.
Find the reason it failed using only systemctl and journalctl, and write
the exit status `systemctl status` shows (such as `status=...`) to
`/root/lab-answers/1`.
2. From the peer, try to SSH to this VM (`10.99.0.1`) as a user that
doesn't exist, and write the name you used to `/root/lab-answers/2`.
Find the attempt in `/var/log/auth.log` and in the journal.
3. List every message of priority err or worse from the last hour, then
only the kernel's, and save that kernel-only list in
`/root/lab-answers/3`.
## Hints
- `systemctl status broken`, `journalctl -u broken`.
- `ip netns exec lepeer ssh [email protected]`; `grep 'Invalid user'
/var/log/auth.log`; `journalctl -u ssh --since "5 min ago"`.
- `journalctl -p err --since "1 hour ago"`; `-k` keeps only the kernel's.
@@ -0,0 +1,21 @@
START=$(state_get start)
ans() { tr 'A-Z' 'a-z' <"/root/lab-answers/$1" 2>/dev/null; }
NAME=$(head -n1 /root/lab-answers/2 2>/dev/null | tr -d '[:space:]')
invalid_in_authlog() { grep -h sshd /var/log/auth.log /var/log/auth.log.1 2>/dev/null | grep -qF "Invalid user $NAME from 10.99.0.2 "; }
invalid_in_journal() { journalctl -q -u ssh -u ssh.socket --since "$START" --no-pager | grep -qF "Invalid user $NAME from 10.99.0.2 "; }
task 1 "broken.service started, and its failure found: 203/EXEC"
need "/etc/systemd/system/broken.service has ExecStart=/usr/local/bin/missing" 'grep -Eq "^ExecStart=/usr/local/bin/missing[[:space:]]*$" /etc/systemd/system/broken.service'
need "systemd has loaded it (daemon-reload)" '[ "$(systemctl show -p LoadState --value broken.service)" = loaded ] && [ "$(systemctl show -p NeedDaemonReload --value broken.service)" = no ]'
need "it was started and failed: journalctl -u broken shows status=203/EXEC" 'journalctl -q -u broken --since "$START" --no-pager | grep -q "status=203/EXEC"'
need "/root/lab-answers/1 has the exit status, 203/EXEC" 'ans 1 | grep -q "203"'
task 2 "A failed SSH login from the peer as a user that doesn't exist"
need "/root/lab-answers/2 holds a user name" '[ -n "$NAME" ]'
need "$NAME isn't a user on this system" '! getent passwd "$NAME"'
need "/var/log/auth.log has Invalid user $NAME from 10.99.0.2" invalid_in_authlog
need "journalctl -u ssh shows the same line" invalid_in_journal
task 3 "The kernel's err-or-worse messages from the last hour in /root/lab-answers/3"
need "/root/lab-answers/3 has the kernel's medium error on sector 4242" 'ans 3 | grep -qF "medium error on sector 4242"'
need "/root/lab-answers/3 has only the kernel's messages (not the backup error)" '! ans 3 | grep -qF "backup of /srv failed"'
@@ -0,0 +1,3 @@
TITLE="2.4 Troubleshooting with logs"
CHAPTER=/linux-terminal/2-system-resources/2-4-troubleshooting-with-logs/
SYSTEMS="debian"
@@ -0,0 +1,8 @@
systemctl stop broken.service 2>/dev/null
rm -f /etc/systemd/system/broken.service
rm -rf /etc/systemd/system/broken.service.d
systemctl daemon-reload
systemctl reset-failed broken.service 2>/dev/null
[ -n "$(state_get had-known-host)" ] || ssh-keygen -q -R 10.99.0.1 -f /root/.ssh/known_hosts >/dev/null 2>&1
rm -f /root/.ssh/known_hosts.old
true
@@ -0,0 +1,10 @@
for f in /etc/systemd/system/broken.service /usr/local/bin/missing; do
[ -e "$f" ] && refuse "$f already exists; the lab needs it absent. Remove it and start the lab again."
done
state_set start "$(date '+%Y-%m-%d %H:%M:%S')"
grep -qs '10\.99\.0\.1' /root/.ssh/known_hosts && state_set had-known-host 1
peer_up
# One error from a program and one from the kernel, for task 3.
logger -t backup -p user.err "backup of /srv failed: no space left on device"
echo '<3>kernel: lab-disk: medium error on sector 4242 (practice lab message)' >/dev/kmsg
true
@@ -0,0 +1,36 @@
# Using Linux Terminal 3.1: SSH keys
An Ed25519 key with a passphrase, installing it for another user by hand,
and what sshd does when a home directory is too open.
## Before you start
The lab creates user `student` (password `Lab-Pass-1`). Make the key as
`student` (`sudo -iu student`); use `sudo` for the steps that need root.
Give the key the passphrase `Lab-Phrase-1`: `le-lab check` needs it to
test the login. The lab checks that user `bob` doesn't exist yet, and
`le-lab reset` removes him and `~/.ssh/lab_key`.
## Tasks
1. Make an Ed25519 key pair with a passphrase and the comment `lab-key`,
saved as `~/.ssh/lab_key`.
2. Create a user `bob` with a password, then install your lab key for him
so you can log in to `bob@localhost` with it. Ubuntu cloud images turn
off password logins over SSH, so ssh-copy-id can't log in there; use
the manual steps instead. After `eval "$(ssh-agent)"; ssh-add
~/.ssh/lab_key`, `ssh -o BatchMode=yes -o
StrictHostKeyChecking=accept-new -i ~/.ssh/lab_key bob@localhost
whoami` prints `bob`.
3. Run `sudo chmod 777 /home/bob`, try the login again, and find out why it
fails. Then fix it.
## Hints
- `ssh-keygen -t ed25519 -C lab-key -f ~/.ssh/lab_key`; `ssh-keygen -l -f`.
- `useradd -m`, `passwd`; then `/home/bob/.ssh` (mode 700) and
`authorized_keys` (mode 600), both owned by `bob`, holding the `.pub`
line. BatchMode doesn't ask about a new host key either: without
`accept-new`, the first try fails with "Host key verification failed."
- `journalctl -u ssh -n 5` or `/var/log/auth.log`: "bad ownership or
modes"; `chmod 750 /home/bob`.
+38
View File
@@ -0,0 +1,38 @@
H=$(state_get home)
K=$H/.ssh/lab_key
BH=$(getent passwd bob | cut -d: -f6)
# key_login: log in as bob with the lab key, unlocked into a temporary copy.
key_login() {
local d r=1
d=$(mktemp -d)
cp "$K" "$d/k" && chmod 600 "$d/k" && ssh-keygen -q -p -P Lab-Phrase-1 -N '' -f "$d/k" >/dev/null 2>&1 &&
[ "$(timeout 15 ssh -o BatchMode=yes -o IdentitiesOnly=yes -o ConnectTimeout=5 -o StrictHostKeyChecking=no \
-o UserKnownHostsFile=/dev/null -o LogLevel=ERROR -i "$d/k" bob@localhost whoami </dev/null 2>/dev/null)" = bob ] && r=0
rm -rf "$d"
return $r
}
bad_modes() {
local n m="bad ownership or modes for directory $BH"
journalctl -q -u ssh --since "$(state_get start)" --no-pager 2>/dev/null | grep -qF "$m" && return 0
n=$(cat "$LAB_STATE/auth-size" 2>/dev/null); n=${n:-0}
[ "$(stat -c %s /var/log/auth.log)" -ge "$n" ] || n=0
tail -c +$((n + 1)) /var/log/auth.log | grep -qF "$m"
}
task 1 "~/.ssh/lab_key: Ed25519, comment lab-key, with a passphrase"
need "$K and $K.pub exist" '[ -f "$K" ] && [ -f "$K.pub" ]'
need "ssh-keygen -l -f $K.pub ends with lab-key (ED25519)" 'ssh-keygen -l -f "$K.pub" | grep -q "lab-key (ED25519)$"'
need "$K is -rw------- and owned by student" '[ "$(stat -c "%a %U" "$K")" = "600 student" ]'
need "$K has a passphrase" '! ssh-keygen -y -P "" -f "$K"'
need "its passphrase is Lab-Phrase-1 (le-lab check needs it)" 'ssh-keygen -y -P Lab-Phrase-1 -f "$K"'
task 2 "User bob with a password, and the lab key logs in as him"
need "user bob exists" 'id bob'
need "bob has a password: passwd -S bob shows P" '[ "$(passwd -S bob | cut -d" " -f2)" = P ]'
need "bob's authorized_keys holds the lab key" 'grep -qF "$(cut -d" " -f2 "$K.pub")" "$BH/.ssh/authorized_keys"'
need "ssh -i ~/.ssh/lab_key bob@localhost whoami prints bob" key_login
task 3 "Home directory too open: found in sshd's log, then fixed"
need "sshd logged bad ownership or modes for directory $BH" bad_modes
need "$BH isn't writable by group or others" '[ -d "$BH" ] && [ -z "$(find "$BH" -maxdepth 0 -perm /022)" ]'
need "the key login works again" key_login
@@ -0,0 +1,3 @@
TITLE="3.1 SSH keys"
CHAPTER=/linux-terminal/3-securing-access/3-1-ssh-keys/
SYSTEMS="debian"
@@ -0,0 +1,7 @@
if id bob >/dev/null 2>&1; then
pkill -KILL -u bob 2>/dev/null
userdel -r bob 2>/dev/null
fi
H=$(state_get home)
[ -n "$H" ] && rm -f "$H/.ssh/lab_key" "$H/.ssh/lab_key.pub"
true
@@ -0,0 +1,8 @@
id bob >/dev/null 2>&1 && refuse "user bob already exists; task 2 creates him. Remove him (userdel -r bob) and start the lab again."
user_add student
H=$(getent passwd student | cut -d: -f6)
state_set home "$H"
rm -f "$H/.ssh/lab_key" "$H/.ssh/lab_key.pub"
state_set start "$(date '+%Y-%m-%d %H:%M:%S')"
wc -c </var/log/auth.log >"$LAB_STATE/auth-size" 2>/dev/null || true
true
@@ -0,0 +1,31 @@
# Using Linux Terminal 3.2: System-wide and per-user security settings
A process limit for one user, a message for every login shell, and a
stricter umask for one user.
## Before you start
The lab creates users `alice` and `student` (password `Lab-Pass-1`).
Work as root (`sudo -i`) for tasks 1 and 2, and as `student` (`sudo -iu
student`) for task 3. The lab saves `/etc/profile`, `/etc/bash.bashrc` and
the limits files, and puts them back on reset; it also removes the files
you add to `/etc/profile.d` and `/etc/security/limits.d`.
## Tasks
1. Limit the user `alice` to 50 processes, as both soft and hard limit, in
a file of its own. `su - alice -c 'ulimit -u; ulimit -Hu'` prints `50`
twice, and other users' limits are unchanged.
2. Make every login shell print `Authorized use only` before the prompt,
without editing `/etc/profile`. `su - alice -c true` prints the message.
3. Give `student` a umask of 077 in every interactive shell: in a new
shell, `umask` prints `0077`, and `touch ~/x; ls -l ~/x` shows
`-rw-------`. Other users keep their umask.
## Hints
- A file in `/etc/security/limits.d/` with `alice`, `-` (or a `soft` and a
`hard` line), `nproc` and `50`. Limits apply at the next login.
- A `.sh` file in `/etc/profile.d/` with an `echo`.
- `~/.bashrc` runs in every interactive bash (and Ubuntu's `~/.profile`
runs it for login shells); `bash` starts a new one to test.
@@ -0,0 +1,32 @@
H=$(state_get home)
saved=$LAB_STATE/saved
login_run() { su - "$1" -c "$2" </dev/null 2>/dev/null; }
# An interactive, non-login bash of USER's, started clean.
interactive_umask() {
local h; h=$(getent passwd "$1" | cut -d: -f6)
runuser -u "$1" -- env -i HOME="$h" USER="$1" LOGNAME="$1" PATH=/usr/bin:/bin TERM=dumb bash -i -c umask </dev/null 2>/dev/null | tail -n1
}
new_files() { # DIR LIST: files in DIR that weren't there at setup
local f
for f in "$1"/*; do
[ -f "$f" ] || continue
grep -qxF "${f##*/}" "$2" 2>/dev/null || printf '%s\n' "$f"
done
}
task 1 "alice limited to 50 processes, soft and hard, in a file of its own"
need "su - alice -c 'ulimit -u; ulimit -Hu' prints 50 twice" '[ "$(login_run alice "ulimit -u; ulimit -Hu" | tail -n2 | paste -sd " ")" = "50 50" ]'
need "a new file in /etc/security/limits.d sets alice's nproc" 'new_files /etc/security/limits.d "$LAB_STATE/limits-d" | xargs -r grep -hEv "^[[:space:]]*#" | grep -Eq "^[[:space:]]*alice[[:space:]].*nproc"'
need "/etc/security/limits.conf is unchanged" 'cmp -s /etc/security/limits.conf "$saved/limits.conf"'
need "other users are unchanged: student's ulimit -u isn't 50" '[ "$(login_run student "ulimit -u" | tail -n1)" != 50 ]'
task 2 "Every login shell prints Authorized use only, from /etc/profile.d"
need "su - alice -c true prints Authorized use only" 'login_run alice true | grep -q "Authorized use only"'
need "a new .sh file in /etc/profile.d prints it" 'new_files /etc/profile.d "$LAB_STATE/profile-d" | grep "\.sh$" | xargs -r grep -l "Authorized use only" | grep -q .'
need "/etc/profile is unchanged" 'cmp -s /etc/profile "$saved/profile"'
task 3 "student's interactive shells have umask 077"
need "a new interactive bash of student's prints umask 0077" '[ "$(interactive_umask student)" = 0077 ]'
need "a login shell of student's has umask 0077 too" '[ "$(su - student -c "bash -i -c umask" </dev/null 2>/dev/null | tail -n1)" = 0077 ]'
need "a file student touches is -rw-------" 'f=$H/.le-check-$$; runuser -u student -- env -i HOME="$H" PATH=/usr/bin:/bin bash -i -c "touch $f" </dev/null 2>/dev/null; m=$(stat -c %a "$f"); rm -f "$f"; [ "$m" = 600 ]'
need "other users keep their umask: alice's isn't 0077" '[ "$(interactive_umask alice)" != 0077 ]'
@@ -0,0 +1,3 @@
TITLE="3.2 System-wide and per-user security settings"
CHAPTER=/linux-terminal/3-securing-access/3-2-security-settings/
SYSTEMS="debian"
@@ -0,0 +1,14 @@
saved=$LAB_STATE/saved
for f in profile bash.bashrc; do
[ -f "$saved/$f" ] && cp -a "$saved/$f" /etc/$f
done
[ -f "$saved/limits.conf" ] && cp -a "$saved/limits.conf" /etc/security/limits.conf
for pair in "/etc/profile.d profile-d" "/etc/security/limits.d limits-d"; do
set -- $pair
[ -f "$LAB_STATE/$2" ] || continue
for f in "$1"/*; do
[ -f "$f" ] || continue
grep -qxF "${f##*/}" "$LAB_STATE/$2" || rm -f "$f"
done
done
true
@@ -0,0 +1,9 @@
user_add alice
user_add student
state_set home "$(getent passwd student | cut -d: -f6)"
saved=$LAB_STATE/saved
mkdir -p "$saved"
cp -a /etc/profile /etc/bash.bashrc /etc/security/limits.conf "$saved/"
ls /etc/profile.d >"$LAB_STATE/profile-d"
ls /etc/security/limits.d >"$LAB_STATE/limits-d"
true
@@ -0,0 +1,37 @@
# Using Linux Terminal 3.3: Password complexity and expiry
Password rules with pam_pwquality, aging and account expiry for one user,
and aging defaults for new accounts.
## Before you start
Work as root (`sudo -i`). The lab creates users `alice` and `pwtest`
(password `Lab-Pass-1`); try passwords out on `pwtest`, for example `echo
'pwtest:lowercase12' | chpasswd`, so alice's settings from task 2 stay as
you made them. The rules only apply when a password is set, so they can't
lock anyone out of logging in. The lab saves `/etc/pam.d/common-password`,
the pwquality settings and `/etc/login.defs`, and puts them back on reset;
reset also removes `libpam-pwquality` again if the lab started without it,
and user `dave` if you made him.
## Tasks
1. Install pam_pwquality and require passwords of at least 10 characters
with at least one digit and one uppercase letter, for every user
including root. `echo 'pwtest:lowercase12' | chpasswd` fails with "BAD
PASSWORD: The password contains less than 1 uppercase letters", and
`echo 'pwtest:Kettle7-Harbor' | chpasswd` succeeds.
2. Make alice's password expire every 60 days with 10 days' warning,
disable her account on the last day of next year, and make her choose a
new password at her next login.
3. Make new accounts' passwords expire after 120 days, without changing
existing users. After `useradd -m dave`, `chage -l dave` shows maximum
120, and `chage -l alice` is unchanged.
## Hints
- `apt install libpam-pwquality`; a file in
`/etc/security/pwquality.conf.d/` (make the directory) with `minlen`,
`dcredit = -1`, `ucredit = -1` and `enforce_for_root`.
- `chage -M -W -E -d 0`; check with `chage -l alice`.
- `PASS_MAX_DAYS` in `/etc/login.defs`.
@@ -0,0 +1,32 @@
sh_field() { getent shadow "$1" | cut -d: -f"$2"; }
# try PASSWORD: root's chpasswd sets it for pwtest.
try() { echo "pwtest:$1" | chpasswd >/dev/null 2>&1; }
NEXT_END=$(( $(date -ud "$(( $(date +%Y) + 1 ))-12-31" +%s) / 86400 ))
new_user_max() {
local u=lecheck$$ m
useradd -M -s /usr/sbin/nologin "$u" >/dev/null 2>&1 || return 1
m=$(sh_field "$u" 5)
userdel "$u" >/dev/null 2>&1
[ "$m" = 120 ]
}
task 1 "pam_pwquality: 10 characters, a digit and an uppercase letter, for root too"
need "libpam-pwquality is installed" 'pkg_installed libpam-pwquality'
need "/etc/pam.d/common-password uses pam_pwquality" 'grep -Eq "^[[:space:]]*password[[:space:]].*pam_pwquality\.so" /etc/pam.d/common-password'
need "even root can't set xq7#lvm-pztw (no uppercase letter)" '! try "xq7#lvm-pztw"'
need "even root can't set Xq#Lvm-PzTw (no digit)" '! try "Xq#Lvm-PzTw"'
need "even root can't set Xq7#Lvm2Z (only 9 characters)" '! try "Xq7#Lvm2Z"'
need "echo pwtest:lowercase12 | chpasswd fails" '! try lowercase12'
need "echo pwtest:Kettle7-Harbor | chpasswd succeeds" 'try Kettle7-Harbor'
task 2 "alice: maximum 60, warning 10, account expires Dec 31 next year, change at next login"
need "alice's maximum password age is 60 days" '[ "$(sh_field alice 5)" = 60 ]'
need "alice is warned 10 days before" '[ "$(sh_field alice 6)" = 10 ]'
need "alice's account expires on the last day of next year" '[ "$(sh_field alice 8)" = "$NEXT_END" ]'
need "alice must change her password at next login" '[ "$(sh_field alice 3)" = 0 ]'
task 3 "New accounts' passwords expire after 120 days; existing users unchanged"
need "/etc/login.defs sets PASS_MAX_DAYS 120" '[ "$(awk "\$1 == \"PASS_MAX_DAYS\" {v = \$2} END {print v}" /etc/login.defs)" = 120 ]'
need "a new account gets maximum 120" new_user_max
need "existing users are unchanged: root's and pwtest's maximum are as before" '[ "$(sh_field root 5)" = "$(cat "$LAB_STATE/max-root")" ] && [ "$(sh_field pwtest 5)" = "$(cat "$LAB_STATE/max-pwtest")" ]'
need "alice's maximum isn't 120" '[ "$(sh_field alice 5)" != 120 ]'
@@ -0,0 +1,3 @@
TITLE="3.3 Password complexity and expiry"
CHAPTER=/linux-terminal/3-securing-access/3-3-password-rules/
SYSTEMS="debian"
@@ -0,0 +1,19 @@
saved=$LAB_STATE/saved
if id dave >/dev/null 2>&1; then
pkill -KILL -u dave 2>/dev/null
userdel -r dave 2>/dev/null
fi
# Packages the task installed (pam-auth-update takes the line out again).
if [ -f "$LAB_STATE/packages" ]; then
new=$(dpkg-query -W -f '${Package}\n' | grep -Fxv -f "$LAB_STATE/packages" |
grep -E 'pwquality|crack|^wamerican$')
[ -n "$new" ] && DEBIAN_FRONTEND=noninteractive apt-get -y -qq purge $new >/dev/null 2>&1
fi
if [ -f "$saved/common-password" ]; then
cp -a "$saved/common-password" /etc/pam.d/common-password
cp -a "$saved/login.defs" /etc/login.defs
if [ -f "$saved/pwquality.conf" ]; then cp -a "$saved/pwquality.conf" /etc/security/pwquality.conf; fi
rm -rf /etc/security/pwquality.conf.d
[ -d "$saved/pwquality.conf.d" ] && cp -a "$saved/pwquality.conf.d" /etc/security/
fi
true
@@ -0,0 +1,12 @@
id dave >/dev/null 2>&1 && refuse "user dave already exists; task 3 creates him. Remove him (userdel -r dave) and start the lab again."
user_add alice
user_add pwtest
saved=$LAB_STATE/saved
mkdir -p "$saved"
cp -a /etc/pam.d/common-password /etc/login.defs "$saved/"
[ -f /etc/security/pwquality.conf ] && cp -a /etc/security/pwquality.conf "$saved/"
[ -d /etc/security/pwquality.conf.d ] && cp -a /etc/security/pwquality.conf.d "$saved/"
dpkg-query -W -f '${Package}\n' >"$LAB_STATE/packages"
# Aging of the existing accounts, for task 3.
for u in root pwtest; do getent shadow $u | cut -d: -f5 >"$LAB_STATE/max-$u"; done
true
@@ -0,0 +1,34 @@
# Using Linux Terminal 3.4: sudo policies
A narrow passwordless rule in a drop-in file, an administrator by group
membership, and the file names sudo skips.
## Before you start
Work as root (`sudo -i`), and write answers to the files in
`/root/lab-answers/`. The lab creates users `alice` and `bob` (password
`Lab-Pass-1`). Edit sudoers files only with `visudo` (`visudo -f
/etc/sudoers.d/NAME`): it refuses to save a file with a mistake in it, and
a broken sudoers file stops sudo working for everyone. Keep the root shell
open while you work. The lab saves `/etc/sudoers` and puts it back on
reset, and removes the files you add to `/etc/sudoers.d`.
## Tasks
1. Let `alice` restart and check the status of the ssh service as root,
with no password, and nothing else. Use a drop-in file. `visudo -c`
reports every file "parsed OK"; as alice, `sudo systemctl restart ssh`
works, and `sudo id` asks for her password and then says she isn't
allowed.
2. Make `bob` an administrator who can run anything with his password,
without editing any sudoers file.
3. Name your drop-in `alice.conf` instead, and see what `sudo -l -U alice`
says. Write that to `/root/lab-answers/3`, then rename the file back.
## Hints
- `alice ALL=(root) NOPASSWD: /usr/bin/systemctl restart ssh,
/usr/bin/systemctl status ssh`; `sudo -l -U alice`; test as alice with
`su - alice`.
- Ubuntu's `/etc/sudoers` gives group `sudo` everything; `usermod -aG`.
- `@includedir /etc/sudoers.d` skips names with a dot or ending in `~`.
@@ -0,0 +1,42 @@
SC=/usr/bin/systemctl
may() { local u=$1; shift; sudo -l -U "$u" "$@" >/dev/null 2>&1; }
# Drop-ins sudo reads (no dot, no ~) that weren't there at setup and name alice.
alice_dropin() {
local f
for f in /etc/sudoers.d/*; do
[ -f "$f" ] || continue
case ${f##*/} in *.* | *~) continue ;; esac
grep -qxF "${f##*/}" "$LAB_STATE/sudoers-d" && continue
grep -qw alice "$f" && return 0
done
return 1
}
only_ssh() {
! may alice /usr/bin/id && ! may alice /bin/bash && ! may alice $SC stop ssh &&
! may alice $SC restart cron && ! may alice $SC restart ssh.socket && ! may alice $SC edit ssh
}
no_password() {
local l
l=$(sudo -l -U alice | sed -n '/may run the following/,$p')
grep -q NOPASSWD <<<"$l" && ! grep -Eq '(^|[^O])PASSWD:' <<<"$l" &&
! runuser -u alice -- sudo -n $SC status ssh 2>&1 >/dev/null </dev/null | grep -qi "password is required"
}
task 1 "alice may restart and check ssh as root, no password, nothing else"
need "visudo -c reports every file parsed OK" 'visudo -c'
need "a drop-in in /etc/sudoers.d (with no dot in its name) names alice" alice_dropin
need "alice may run systemctl restart ssh as root" 'may alice $SC restart ssh'
need "alice may run systemctl status ssh as root" 'may alice $SC status ssh'
need "alice needs no password for them (NOPASSWD)" no_password
need "alice may run nothing else (not id, a shell, or other systemctl commands)" only_ssh
task 2 "bob is an administrator through group sudo"
need "groups bob includes sudo" 'id -nG bob | grep -qw sudo'
need "sudo -l -U bob shows (ALL : ALL) ALL" 'sudo -l -U bob | grep -qF "(ALL : ALL) ALL"'
need "bob still needs his password (no NOPASSWD)" '! sudo -l -U bob | grep -q NOPASSWD'
need "no sudoers file names bob" '! grep -rqw bob /etc/sudoers /etc/sudoers.d'
task 3 "sudo skips alice.conf: its answer saved, file renamed back"
need "/root/lab-answers/3 has what sudo said: alice is not allowed to run sudo" 'grep -qi "not allowed to run sudo" /root/lab-answers/3'
need "no file with a dot (or ending in ~) in /etc/sudoers.d names alice" '! find /etc/sudoers.d -type f \( -name "*.*" -o -name "*~" \) -exec grep -qw alice {} \; -print | grep -q .'
need "alice's drop-in is back in use" 'alice_dropin && may alice $SC restart ssh'
@@ -0,0 +1,3 @@
TITLE="3.4 sudo policies"
CHAPTER=/linux-terminal/3-securing-access/3-4-sudo-policies/
SYSTEMS="debian"
@@ -0,0 +1,6 @@
for f in /etc/sudoers.d/* /etc/sudoers.d/.*; do
[ -f "$f" ] || continue
grep -qxF "${f##*/}" "$LAB_STATE/sudoers-d" 2>/dev/null || rm -f "$f"
done
[ -f "$LAB_STATE/sudoers" ] && cp -a "$LAB_STATE/sudoers" /etc/sudoers
true
@@ -0,0 +1,6 @@
visudo -c >/dev/null 2>&1 || refuse "visudo -c finds a problem in the sudoers files; fix it and start the lab again."
user_add alice
user_add bob
cp -a /etc/sudoers "$LAB_STATE/sudoers"
ls -A /etc/sudoers.d >"$LAB_STATE/sudoers-d"
true
@@ -0,0 +1,33 @@
# Using Linux Terminal 3.5: Users and groups
Creating a user with useradd, adding groups, locking an account, and
removing one and finding what it left behind.
## Before you start
Work as root (`sudo -i`), and write answers to the files in
`/root/lab-answers/`. The lab checks that user `hana`, UID 1500 and group
`qa` don't exist yet. It creates user `lou`, who has files outside his
home directory. Task 4 removes `lou` rather than `hana`, so that hana stays
for grading. `le-lab reset` removes hana, `qa` and lou's files.
## Tasks
1. With useradd, create `hana` with a home directory, bash, the comment
"Hana Ito" and UID 1500, in a new group `qa` as well as her own. Set her
a password.
2. Add hana to `adm` without removing her from `qa`.
3. Stop hana logging in by any means, without deleting anything: `passwd
-S hana` shows `L`, `chage -l hana` shows an account expiry date in the
past, and `su - hana` fails.
4. Remove `lou`'s account and home directory, then find anything he left
on the root filesystem, and write the paths to `/root/lab-answers/4`.
## Hints
- `groupadd`; `useradd -m -s -c -u -G`; `passwd`; check with `getent
passwd hana`, `id hana`, `passwd -S hana`.
- `usermod -aG`: without `-a`, `-G` replaces the list.
- `usermod -L` (or `passwd -l`) locks the password only; `usermod -e 1` or
`chage -E 0` expires the account, which also stops SSH keys.
- `userdel -r`, then `find / -xdev -nouser`.
@@ -0,0 +1,29 @@
pw() { getent passwd hana | cut -d: -f"$1"; }
today=$(( $(date -u +%s) / 86400 ))
ans4() { cat /root/lab-answers/4 2>/dev/null; }
task 1 "hana: UID 1500, Hana Ito, /home/hana, bash, in qa, with a password"
need "user hana exists" 'id hana'
need "getent passwd hana shows UID 1500" '[ "$(pw 3)" = 1500 ]'
need "her comment is Hana Ito" '[ "$(pw 5 | cut -d, -f1)" = "Hana Ito" ]'
need "her home is /home/hana, and it exists" '[ "$(pw 6)" = /home/hana ] && [ -d /home/hana ] && [ "$(stat -c %U /home/hana)" = hana ]'
need "her shell is /bin/bash" '[ "$(pw 7)" = /bin/bash ]'
need "she has a group of her own" 'getent group hana | grep -q . && [ "$(id -gn hana)" = hana ]'
need "id hana lists qa" 'id -nG hana | grep -qw qa'
need "she has a password set" 'getent shadow hana | cut -d: -f2 | grep -Eq "^!?\\$"'
task 2 "hana in adm as well as qa"
need "id hana lists adm" 'id -nG hana | grep -qw adm'
need "id hana still lists qa" 'id -nG hana | grep -qw qa'
task 3 "hana can't log in by any means; nothing deleted"
need "user hana and /home/hana still exist" 'id hana && [ -d /home/hana ]'
need "passwd -S hana shows L" '[ "$(passwd -S hana | cut -d" " -f2)" = L ]'
need "chage -l hana shows an account expiry date in the past" 'e=$(getent shadow hana | cut -d: -f8); [ -n "$e" ] && [ "$e" -le "$today" ]'
need "su - hana fails" '! su - hana -c true </dev/null'
task 4 "lou removed with his home; what he left in /root/lab-answers/4"
need "lou's account is gone" '! getent passwd lou'
need "/home/lou is gone" '[ ! -e /home/lou ]'
need "/root/lab-answers/4 lists /var/tmp/lou-build.log" 'ans4 | grep -qF /var/tmp/lou-build.log'
need "/root/lab-answers/4 lists /srv/lou-reports" 'ans4 | grep -qF /srv/lou-reports'
@@ -0,0 +1,3 @@
TITLE="3.5 Users and groups"
CHAPTER=/linux-terminal/3-securing-access/3-5-users-and-groups/
SYSTEMS="debian"
@@ -0,0 +1,7 @@
if id hana >/dev/null 2>&1; then
pkill -KILL -u hana 2>/dev/null
userdel -r hana 2>/dev/null
fi
groupdel qa 2>/dev/null
rm -rf /var/tmp/lou-build.log /srv/lou-reports
true
@@ -0,0 +1,14 @@
id hana >/dev/null 2>&1 && refuse "user hana already exists; task 1 creates her. Remove her (userdel -r hana) and start the lab again."
getent passwd 1500 >/dev/null && refuse "UID 1500 is already taken (by $(getent passwd 1500 | cut -d: -f1)); task 1 needs it."
getent group qa >/dev/null && refuse "group qa already exists; task 1 creates it. Remove it and start the lab again."
id lou >/dev/null 2>&1 && refuse "user lou already exists; the lab creates him. Remove him and start the lab again."
user_add lou -c "Lou Park"
# Files lou leaves outside his home directory. Written by root first,
# then given to lou: root can't write to another user's file in a sticky
# directory such as /var/tmp (fs.protected_regular).
rm -rf /var/tmp/lou-build.log /srv/lou-reports
echo "build 41 finished" >/var/tmp/lou-build.log
mkdir -p /srv/lou-reports
echo "quarter,total" >/srv/lou-reports/q3.csv
chown lou:lou /var/tmp/lou-build.log /srv/lou-reports /srv/lou-reports/q3.csv
true
@@ -0,0 +1,33 @@
# Using Linux Terminal 3.6: Ownership and permissions
A shared group directory with setgid and the sticky bit, private files in
octal, and finding setuid programs.
## Before you start
Work as root (`sudo -i`) for tasks 1 and 3, and as `student` (`sudo -iu
student`) for task 2. Write answers to the files in `/root/lab-answers/`.
The lab creates users `ana`, `ben` and `student` (password `Lab-Pass-1`),
and checks that `/srv/finance` and group `finance` don't exist yet;
`le-lab reset` removes them.
## Tasks
1. Create `/srv/finance` owned by root and the group `finance` (create the
group, with `ana` and `ben` as its members), where members can create
and edit files, new files belong to `finance`, members can't delete each
other's files, and others have no access. `ls -ld /srv/finance` shows
`drwxrws--T root finance` (mode 3770).
2. As `student`, make `~/private` a directory only you can enter, and
`~/private/notes.txt` a file only you can read and write, using octal.
3. List every setuid file on the root filesystem, find the one that lets
users change their own password, and write its full path to
`/root/lab-answers/3`.
## Hints
- `groupadd`, `usermod -aG` (or `gpasswd -a`); `chown root:finance`,
`chmod 3770`. Test with `su - ana -c 'touch /srv/finance/a'` and `su -
ben -c 'rm /srv/finance/a'`.
- `chmod 700`, `chmod 600`; `stat -c '%a %n'`.
- `find / -xdev -perm -4000 -type f 2>/dev/null`; `ls -l` shows the `s`.
@@ -0,0 +1,27 @@
H=$(state_get home)
F=/srv/finance
# shared_dir_works: ana's new file gets group finance, ben can create one
# too but can't remove ana's.
shared_dir_works() {
local a=$F/.le-check-ana-$$ b=$F/.le-check-ben-$$ r=1
runuser -u ana -- touch "$a" 2>/dev/null && [ "$(stat -c %G "$a")" = finance ] &&
runuser -u ben -- touch "$b" 2>/dev/null &&
! runuser -u ben -- rm -f "$a" 2>/dev/null && [ -e "$a" ] && r=0
rm -f "$a" "$b"
return $r
}
task 1 "/srv/finance: root:finance, mode 3770, shared by ana and ben"
need "group finance exists" 'getent group finance'
need "ana and ben are members of finance" 'id -nG ana | grep -qw finance && id -nG ben | grep -qw finance'
need "$F is a directory owned by root and group finance" '[ -d $F ] && [ "$(stat -c "%U %G" $F)" = "root finance" ]'
need "ls -ld $F shows drwxrws--T (mode 3770)" '[ "$(stat -c %a $F)" = 3770 ]'
need "a member's new file belongs to finance, and the other member can't delete it" shared_dir_works
task 2 "~/private is 700 and ~/private/notes.txt is 600, owned by student"
need "$H/private is a directory with mode 700" '[ -d "$H/private" ] && [ "$(stat -c %a "$H/private")" = 700 ]'
need "$H/private/notes.txt is a file with mode 600" '[ -f "$H/private/notes.txt" ] && [ "$(stat -c %a "$H/private/notes.txt")" = 600 ]'
need "both belong to student" '[ "$(stat -c %U "$H/private" "$H/private/notes.txt" | sort -u)" = student ]'
task 3 "The setuid program for changing passwords in /root/lab-answers/3"
need "/root/lab-answers/3 holds /usr/bin/passwd" '[ "$(answer 3)" = /usr/bin/passwd ]'
@@ -0,0 +1,3 @@
TITLE="3.6 Ownership and permissions"
CHAPTER=/linux-terminal/3-securing-access/3-6-ownership-and-permissions/
SYSTEMS="debian"
@@ -0,0 +1,6 @@
rm -rf /srv/finance
H=$(state_get home)
[ -n "$H" ] && rm -rf "$H/private"
for u in ana ben; do gpasswd -d "$u" finance >/dev/null 2>&1; done
groupdel finance 2>/dev/null
true
@@ -0,0 +1,9 @@
[ -e /srv/finance ] && refuse "/srv/finance already exists; task 1 creates it. Remove it and start the lab again."
getent group finance >/dev/null && refuse "group finance already exists; task 1 creates it. Remove it and start the lab again."
user_add ana
user_add ben
user_add student
H=$(getent passwd student | cut -d: -f6)
state_set home "$H"
rm -rf "$H/private"
true
@@ -1,12 +1,7 @@
if [ -f "$LAB_STATE/ssh.tar" ]; then
rm -f /etc/ssh/sshd_config.d/* /etc/ssh/revoked.krl /etc/ssh/*-cert.pub
tar -C /etc -xf "$LAB_STATE/ssh.tar"
if is_rhel; then
systemctl restart sshd
else
systemctl restart ssh.socket 2>/dev/null
systemctl restart ssh.service 2>/dev/null
fi
sshd_restart
fi
rm -rf /root/sshca /srv/jail /usr/local/bin/lab-python
pkg_installed usbguard && [ ! -f "$LAB_STATE/usbguard.tar" ] && systemctl disable --now --quiet usbguard 2>/dev/null
@@ -0,0 +1,36 @@
# Ubuntu Desktop 1.1: Network connections
Making a NetworkManager profile with a fixed address, switching it to
DHCP while keeping a DNS server, and seeing where Ubuntu stores it. The
lab works on a spare interface, never on the one you're connected over.
The chapter's Wi-Fi task needs Wi-Fi hardware and isn't part of the lab.
## Before you start
The lab adds a spare wired interface `lab0` (a dummy interface that
comes back at every boot). If NetworkManager isn't installed, as on
Ubuntu Server, the lab installs it; it leaves the interfaces it already
finds alone, and reset removes it again. Don't activate the profile.
## Tasks
1. Create a profile `office` for interface `lab0` with the fixed address
10.10.10.50/24, gateway 10.10.10.1 and DNS 10.10.10.1, without
activating it.
2. Make the profile use DHCP for its address but keep the DNS server
10.10.10.1. Task 1 stays passed once you've done this.
When `le-lab check` passes, remove the profile as the chapter shows, or
leave that to `le-lab reset`.
## Hints
- `nmcli connection add type ethernet ifname lab0 con-name office
ipv4.method manual ipv4.addresses ... ipv4.gateway ... ipv4.dns ...
connection.autoconnect no`. `nmcli -g ipv4.method,ipv4.addresses
connection show office` prints `manual` and `10.10.10.50/24`, and `sudo
ls /etc/netplan/` shows a new `90-NM-` file.
- `nmcli connection modify office ipv4.method auto`, clearing the old
address with `ipv4.addresses "" ipv4.gateway ""`, and
`ipv4.ignore-auto-dns yes` so DHCP's DNS servers aren't used.
- `nmcli connection delete office`.
@@ -0,0 +1,18 @@
get() { nmcli -g "$1" connection show office 2>/dev/null; }
uuid=$(get connection.uuid)
is_task2() { [ "$(get ipv4.method)" = auto ] && [ -z "$(get ipv4.addresses)" ] && [ -z "$(get ipv4.gateway)" ] && [ "$(get ipv4.ignore-auto-dns)" = yes ]; }
task 1 "Profile office for lab0: 10.10.10.50/24, not active"
need "a profile office exists (nmcli connection show)" '[ -n "$uuid" ]'
need "it's for interface lab0" '[ "$(get connection.interface-name)" = lab0 ]'
need "it has the address 10.10.10.50/24 and gateway 10.10.10.1 (or DHCP after task 2)" '{ [ "$(get ipv4.method)" = manual ] && [ "$(get ipv4.addresses)" = 10.10.10.50/24 ] && [ "$(get ipv4.gateway)" = 10.10.10.1 ]; } || is_task2'
need "its DNS server is 10.10.10.1" '[ "$(get ipv4.dns)" = 10.10.10.1 ]'
need "it isn't active" '! nmcli -g NAME connection show --active | grep -qx office'
need "it's stored as /etc/netplan/90-NM-$uuid.yaml" '[ -f "/etc/netplan/90-NM-$uuid.yaml" ]'
task 2 "office uses DHCP but keeps DNS 10.10.10.1"
need "a profile office exists" '[ -n "$uuid" ]'
need "ipv4.method is auto" '[ "$(get ipv4.method)" = auto ]'
need "the old address and gateway are cleared" '[ -z "$(get ipv4.addresses)" ] && [ -z "$(get ipv4.gateway)" ]'
need "DNS servers from DHCP are ignored (ipv4.ignore-auto-dns yes)" '[ "$(get ipv4.ignore-auto-dns)" = yes ]'
need "the DNS server is still 10.10.10.1" '[ "$(get ipv4.dns)" = 10.10.10.1 ]'
@@ -0,0 +1,3 @@
TITLE="1.1 Network connections"
CHAPTER=/ubuntu-desktop/1-networking/1-1-network-connections/
SYSTEMS="debian"
@@ -0,0 +1,17 @@
if command -v nmcli >/dev/null; then
nmcli -g UUID,NAME connection show 2>/dev/null | while IFS=: read -r uuid name; do
[ "$name" = office ] || [ "$(nmcli -g connection.interface-name connection show "$uuid" 2>/dev/null)" = lab0 ] || continue
nmcli connection delete "$uuid" >/dev/null 2>&1
rm -f "/etc/netplan/90-NM-$uuid.yaml"
done
fi
if [ -f /etc/systemd/system/le-lab-lab0.service ]; then
systemctl disable --now --quiet le-lab-lab0.service 2>/dev/null
rm -f /etc/systemd/system/le-lab-lab0.service
systemctl daemon-reload
fi
ip link del lab0 2>/dev/null
if [ -s "$LAB_STATE/pkgs-added" ]; then
DEBIAN_FRONTEND=noninteractive apt-get -y -qq purge $(cat "$LAB_STATE/pkgs-added") >/dev/null 2>&1
fi
true
@@ -0,0 +1,34 @@
installed_pkgs() { dpkg-query -W -f '${db:Status-Abbrev} ${Package}\n' 2>/dev/null | awk '$1 ~ /^.i/ {print $2}' | sort -u; }
[ -e /sys/class/net/lab0 ] && refuse "an interface lab0 already exists; remove it first"
if command -v nmcli >/dev/null; then
systemctl is-active --quiet NetworkManager || refuse "NetworkManager is installed but not running; start it first"
nmcli -g NAME connection show | grep -qx office && refuse "a NetworkManager profile office already exists; delete it first"
nmcli -g connection.interface-name connection show 2>/dev/null | grep -qx lab0 && refuse "a NetworkManager profile for lab0 already exists; delete it first"
else
# Ubuntu Server: NetworkManager ships with every wired interface
# unmanaged unless netplan hands it over, so the connection stays
# with systemd-networkd.
installed_pkgs >"$LAB_STATE/pkgs-before"
pkg_install network-manager
comm -13 "$LAB_STATE/pkgs-before" <(installed_pkgs) >"$LAB_STATE/pkgs-added"
systemctl enable --now --quiet NetworkManager
for i in $(seq 20); do nmcli general >/dev/null 2>&1 && break; sleep 0.5; done
fi
cat >/etc/systemd/system/le-lab-lab0.service <<'UNIT'
[Unit]
Description=Spare interface lab0 for a LINUXexpert practice lab
Before=network-pre.target NetworkManager.service
Wants=network-pre.target
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/bin/sh -c 'ip link show lab0 >/dev/null 2>&1 || ip link add lab0 type dummy'
ExecStop=/bin/sh -c 'ip link del lab0 2>/dev/null || true'
[Install]
WantedBy=multi-user.target
UNIT
systemctl daemon-reload
systemctl enable --now --quiet le-lab-lab0.service
+38
View File
@@ -0,0 +1,38 @@
# Ubuntu Desktop 1.2: Layer 2: MAC addresses, ARP and broadcast
Finding MAC addresses and the maker's prefix, watching an ARP exchange,
and checking that an address is free before you use it.
## Before you start
The lab sets up a second machine, the peer, at `10.99.0.2` (name `peer`).
It's on the same network as this VM's interface `le-host` (`10.99.0.1`),
so the two can talk directly over Ethernet. Two of the addresses
`10.99.0.21`, `10.99.0.22` and `10.99.0.23` are also in use on that
network; one is free. `iputils-arping` isn't installed. Write answers to
the files in `/root/lab-answers/`.
## Tasks
1. Find your machine's MAC address and the maker's prefix in it, and your
default gateway's MAC address. Use the interface your default route
goes out of. Write your MAC on line 1 of `/root/lab-answers/1`, the
prefix (its first three bytes) on line 2, and the gateway's MAC on
line 3.
2. Empty the neighbor table, then watch the ARP exchange as you ping the
peer. Write the peer's MAC address, from its reply, to
`/root/lab-answers/2`.
3. Check which of `10.99.0.21`, `10.99.0.22` and `10.99.0.23` is free
before giving it to a new machine (install `iputils-arping` first).
Write the free address to `/root/lab-answers/3`.
## Hints
- `ip -br link`; `ip route | grep default` gives the gateway's IP; after
`ping -c1` to it, `ip neigh show` lists it with `lladdr` and its MAC.
- `sudo ip neigh flush dev le-host`, then `sudo tcpdump -i le-host -e -n
arp` in one terminal and `ping -c1 10.99.0.2` in another: look for
`Request who-has` to `ff:ff:ff:ff:ff:ff` and `Reply ... is-at`.
- `sudo arping -D -c 2 -I le-host ADDRESS; echo $?` prints `Received 0
response(s)` and `0` for a free address, and a reply and `1` for one in
use.
+21
View File
@@ -0,0 +1,21 @@
norm() { tr A-Z a-z | tr -d '[:space:]' | tr '-' ':'; }
line() { sed -n "${2}p" "/root/lab-answers/$1" 2>/dev/null | norm; }
dev=$(ip route show default | awk '{for (i = 1; i < NF; i++) if ($i == "dev") {print $(i + 1); exit}}')
gw=$(ip route show default | awk '{print $3; exit}')
mac=$(cat "/sys/class/net/$dev/address" 2>/dev/null)
gw_mac() {
ping -c1 -W2 "$gw" >/dev/null 2>&1
ip neigh show "$gw" dev "$dev" | awk '{for (i = 1; i < NF; i++) if ($i == "lladdr") print $(i + 1)}' | head -n1
}
task 1 "Your MAC, its maker's prefix, and the gateway's MAC"
need "line 1 of /root/lab-answers/1 is the MAC of $dev" '[ -n "$mac" ] && [ "$(line 1 1)" = "$mac" ]'
need "line 2 is the maker's prefix (its first three bytes)" '[ "$(line 1 2 | tr -d :)" = "$(printf %s "$mac" | cut -c1-8 | tr -d :)" ]'
need "line 3 is the MAC of the gateway $gw" 'g=$(gw_mac); [ -n "$g" ] && [ "$(line 1 3)" = "$g" ]'
task 2 "The peer's MAC from its ARP reply"
need "/root/lab-answers/2 is the peer's MAC" '[ "$(line 2 1)" = "$(in_peer cat /sys/class/net/le-peer/address)" ]'
task 3 "The free address found with arping"
need "iputils-arping is installed" 'pkg_installed iputils-arping'
need "/root/lab-answers/3 is the free address" '[ "$(line 3 1)" = "$(state_get free)" ]'
+3
View File
@@ -0,0 +1,3 @@
TITLE="1.2 Layer 2: MAC addresses, ARP and broadcast"
CHAPTER=/ubuntu-desktop/1-networking/1-2-layer-2/
SYSTEMS="debian"
+11
View File
@@ -0,0 +1,11 @@
if [ -f /etc/systemd/system/le-lab-l2.service ]; then
systemctl disable --quiet le-lab-l2.service 2>/dev/null
rm -f /etc/systemd/system/le-lab-l2.service
systemctl daemon-reload
fi
if [ -n "$(state_get had-arping)" ]; then
pkg_installed iputils-arping || DEBIAN_FRONTEND=noninteractive apt-get -y -qq install iputils-arping >/dev/null 2>&1
elif pkg_installed iputils-arping; then
DEBIAN_FRONTEND=noninteractive apt-get -y -qq purge iputils-arping >/dev/null 2>&1
fi
true
+28
View File
@@ -0,0 +1,28 @@
[ -e /etc/systemd/system/le-lab-l2.service ] && refuse "le-lab-l2.service exists; run le-lab reset first"
ip route show default | grep -q . || refuse "this VM has no default route; task 1 needs one"
if pkg_installed iputils-arping; then
state_set had-arping 1
DEBIAN_FRONTEND=noninteractive apt-get -y -qq purge iputils-arping >/dev/null
fi
peer_up
# The peer gets a fixed MAC address and two of the three candidate
# addresses, again at every boot.
free=$((21 + RANDOM % 3))
state_set free "10.99.0.$free"
used=$(for n in 21 22 23; do [ "$n" = "$free" ] || printf ' 10.99.0.%s/24' "$n"; done)
cat >/etc/systemd/system/le-lab-l2.service <<UNIT
[Unit]
Description=LINUXexpert practice lab: addresses on the peer
After=le-lab-peer.service
Requires=le-lab-peer.service
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/bin/sh -c 'ip -n lepeer link set le-peer address 02:4c:45:00:00:02; for a in$used; do ip -n lepeer addr add \$\$a dev le-peer 2>/dev/null; done; true'
[Install]
WantedBy=multi-user.target
UNIT
systemctl daemon-reload
systemctl enable --now --quiet le-lab-l2.service
@@ -0,0 +1,31 @@
# Ubuntu Desktop 2.1: LTS releases and version numbers
Finding this system's release and point release, when its support ends,
and which Ubuntu releases are still supported.
## Before you start
The lab installs `distro-info` if it's missing and changes nothing else.
Write your answers to the files in `/root/lab-answers/`, one item per
line.
## Tasks
1. Find this system's release, code name and point release. Write the
point release (such as `24.04.5`) on line 1 of `/root/lab-answers/1`
and the code name on line 2.
2. Find the date standard support ends for this release, and for the
latest LTS. Write them on lines 1 and 2 of `/root/lab-answers/2`, as
`YYYY-MM-DD` (or at least the month, such as `May 2029`).
3. List every Ubuntu release still supported today, and say which of them
are LTS releases. Write one release per line to `/root/lab-answers/3`
as its version number, followed by `LTS` for the LTS ones (such as
`24.04 LTS`). Leave out the release still in development.
## Hints
- `lsb_release -a`, `/etc/os-release`.
- The `eol` column of `/usr/share/distro-info/ubuntu.csv`;
`hwe-support-status --verbose`; `ubuntu-distro-info --lts`.
- `ubuntu-distro-info --supported --fullname`, and `--devel` for the
release still being developed.
@@ -0,0 +1,36 @@
line() { sed -n "${2}p" "/root/lab-answers/$1" 2>/dev/null | sed 's/^[[:space:]]*//; s/[[:space:]]*$//'; }
csv=/usr/share/distro-info/ubuntu.csv
. /etc/os-release
eol_of() { awk -F, -v s="$1" '$3 == s {print $6}' "$csv"; }
# month LINE: YYYY-MM from an answer such as 2029-05-31 or May 2029.
month() {
local a=$1
if [[ $a =~ ^([0-9]{4}-[0-9]{2}) ]]; then printf '%s' "${BASH_REMATCH[1]}"; return; fi
date -d "1 $a" +%Y-%m 2>/dev/null
}
# The supported releases as "VERSION" or "VERSION LTS", sorted.
expected() {
local skip=${1:-}
ubuntu-distro-info --supported --fullname 2>/dev/null | while read -r _ ver rest; do
[ -n "$skip" ] && [ "$(ubuntu-distro-info --series="$skip" -r 2>/dev/null | cut -d' ' -f1)" = "$ver" ] && continue
case $rest in LTS*) echo "$ver LTS" ;; *) echo "$ver" ;; esac
done | sort
}
given() {
grep -v '^[[:space:]]*$' /root/lab-answers/3 2>/dev/null | while read -r l; do
v=$(grep -Eo '[0-9]{2}\.[0-9]{2}' <<<"$l" | head -n1)
[ -n "$v" ] || continue
if grep -qi 'lts' <<<"$l"; then echo "$v LTS"; else echo "$v"; fi
done | sort -u
}
task 1 "This system's point release and code name"
need "line 1 of /root/lab-answers/1 is the point release ($VERSION_ID.x)" '[ "$(line 1 1)" = "${VERSION%% *}" ]'
need "line 2 is the code name" 'line 1 2 | tr A-Z a-z | grep -qw "$VERSION_CODENAME"'
task 2 "End of standard support: this release and the latest LTS"
need "line 1 of /root/lab-answers/2 is this release's end of support" '[ "$(month "$(line 2 1)")" = "$(eol_of "$VERSION_CODENAME" | cut -c1-7)" ]'
need "line 2 is the latest LTS's end of support" '[ "$(month "$(line 2 2)")" = "$(eol_of "$(ubuntu-distro-info --lts)" | cut -c1-7)" ]'
task 3 "Every supported release, LTS ones marked"
need "/root/lab-answers/3 lists exactly the supported releases, with LTS marked and the development release left out" '[ "$(given)" = "$(expected "$(ubuntu-distro-info --devel 2>/dev/null)")" ]'
@@ -0,0 +1,3 @@
TITLE="2.1 LTS releases and version numbers"
CHAPTER=/ubuntu-desktop/2-open-source-community/2-1-lts-releases/
SYSTEMS="debian"
@@ -0,0 +1,4 @@
if [ -n "$(state_get added-distro-info)" ]; then
DEBIAN_FRONTEND=noninteractive apt-get -y -qq purge distro-info >/dev/null 2>&1
fi
true
@@ -0,0 +1,4 @@
if ! command -v ubuntu-distro-info >/dev/null; then
pkg_install distro-info
state_set added-distro-info 1
fi
@@ -0,0 +1,30 @@
# Ubuntu Desktop 2.3: Open source licenses
Reading the licenses of installed packages, telling copyleft from
permissive, and finding which archive component a package comes from.
## Before you start
The lab changes nothing. Write your answers to the files in
`/root/lab-answers/`, one item per line.
## Tasks
1. Find the license of the `bash` package and of the `openssl` package,
and say which of them could be built into a closed-source product.
Write bash's main license on line 1 of `/root/lab-answers/1`,
openssl's on line 2, and the package that could be built in on line 3.
2. Find which component `ubuntu-drivers-common` comes from, and which
component NVIDIA's driver `nvidia-driver-535` is in. Write them on
lines 1 and 2 of `/root/lab-answers/2`.
3. Count how many installed packages carry a GPL license of any version,
and write the number to `/root/lab-answers/3`.
## Hints
- `grep '^License:' /usr/share/doc/PACKAGE/copyright`; a few files in a
package can have other licenses than the main one.
- `apt-cache show PACKAGE | grep -m1 Section`: a section with no prefix
is in `main`, otherwise the prefix is the component.
- `grep -l` over `/usr/share/doc/*/copyright`, then `wc -l`. Rough
counts are fine.
@@ -0,0 +1,15 @@
line() { sed -n "${2}p" "/root/lab-answers/$1" 2>/dev/null | sed 's/^[[:space:]]*//; s/[[:space:]]*$//' | tr A-Z a-z; }
component() { local s; s=$(apt-cache show "$1" 2>/dev/null | awk '$1 == "Section:" {print $2; exit}'); [ -n "$s" ] || return; case $s in */*) printf '%s' "${s%%/*}" ;; *) printf main ;; esac; }
gpl_count() { grep -l '^License: .*GPL' /usr/share/doc/*/copyright 2>/dev/null | wc -l; }
task 1 "Licenses of bash and openssl"
need "line 1 of /root/lab-answers/1 is bash's license (GPL version 3 or later)" 'line 1 1 | grep -Eq "gpl.?(v)?3"'
need "line 2 is openssl's license (Apache 2.0)" 'line 1 2 | grep -Eq "apache"'
need "line 3 is the package that could be built into a closed-source product" '[ "$(line 1 3)" = openssl ]'
task 2 "Components of ubuntu-drivers-common and nvidia-driver-535"
need "line 1 of /root/lab-answers/2 is ubuntu-drivers-common's component" 'c=$(component ubuntu-drivers-common); [ -n "$c" ] && [ "$(line 2 1)" = "$c" ]'
need "line 2 is nvidia-driver-535's component" 'c=$(component nvidia-driver-535); [ -n "$c" ] && [ "$(line 2 2)" = "$c" ]'
task 3 "Number of installed packages under a GPL license"
need "/root/lab-answers/3 has the count (within 10%)" 'a=$(line 3 1); n=$(gpl_count); [[ $a =~ ^[0-9]+$ ]] && [ $((a * 10)) -ge $((n * 9)) ] && [ $((a * 10)) -le $((n * 11)) ]'
@@ -0,0 +1,3 @@
TITLE="2.3 Open source licenses"
CHAPTER=/ubuntu-desktop/2-open-source-community/2-3-open-source-licenses/
SYSTEMS="debian"
@@ -0,0 +1 @@
true
@@ -0,0 +1,2 @@
# Nothing to set up: every task is a finding.
true
@@ -0,0 +1,45 @@
# Ubuntu Desktop 3.1: Boot problems: GRUB 2 and the initramfs
Making the GRUB menu show at every boot with kernel messages instead of
the logo, recovery mode, getting a system back from `grub rescue>`, and
rebuilding the initramfs. Tasks 2 and 3 need the VM's console.
## Before you start
The lab saves `/etc/default/grub` and `/etc/default/grub.d/` so reset can
put them back (and runs `update-grub` if they changed). It changes
nothing else. On cloud images,
`/etc/default/grub.d/50-cloudimg-settings.cfg` is read after
`/etc/default/grub` and sets `GRUB_TIMEOUT=0`.
## Tasks
1. Make the GRUB menu appear for 10 seconds at every boot, and make every
boot show kernel messages instead of the logo (no `quiet` or `splash`
on the kernel command line). Reboot to see it.
2. Boot once into recovery mode, get a root shell, and make sure `/` is
writable. Not graded: in that shell, `cat /proc/cmdline` contains
`recovery`, `systemctl is-active friendly-recovery.service` prints
`active`, and `touch /root/recovery-test` succeeds.
3. Rename `/boot/grub` to `/boot/grub.bak`, reboot, and get the system
booted from `grub rescue>`. Then repair it for good. Not graded: after
`sudo mv /boot/grub.bak /boot/grub`, `sudo grub-install DISK` prints
`Installation finished. No error reported.`, and the next reboot shows
the normal menu. Take a snapshot of the VM before this one.
4. Rebuild the initramfs of every installed kernel.
## Hints
- `GRUB_TIMEOUT_STYLE=menu`, `GRUB_TIMEOUT=10` and
`GRUB_CMDLINE_LINUX_DEFAULT`, in `/etc/default/grub` or a file in
`/etc/default/grub.d/` that sorts after the cloud image's; then `sudo
update-grub`. After a reboot, `cat /proc/cmdline`.
- In the menu: Advanced options, then the `(recovery mode)` entry, then
`root`. `mount -o remount,rw /` if `/` is read-only.
- At `grub rescue>`: `set` shows the prefix GRUB looked in; `ls
(hd0,gpt16)/` finds `grub.bak` (on cloud images `/boot` is its own
partition). Then `set prefix=(hd0,gpt16)/grub.bak`, `set
root=(hd0,gpt16)`, `insmod normal`, `normal`. `lsblk` shows the disk
for `grub-install`.
- `sudo update-initramfs -u -k all` prints a `Generating` line for each
kernel in `ls /boot/vmlinuz-*`.
@@ -0,0 +1,35 @@
cfg=/boot/grub/grub.cfg
# Effective settings: /etc/default/grub, then grub.d/*.cfg, as
# grub-mkconfig reads them.
setting() {
(
. /etc/default/grub >/dev/null 2>&1
for f in /etc/default/grub.d/*.cfg; do [ -e "$f" ] && . "$f" >/dev/null 2>&1; done
eval "printf '%s' \"\${$1:-}\""
)
}
first_linux() { awk '$1 == "linux" {print; exit}' "$cfg"; }
kernels_rebuilt() {
local k v n=0
for k in /boot/vmlinuz-*; do
[ -e "$k" ] || continue
v=${k#/boot/vmlinuz-}
[ "/boot/initrd.img-$v" -nt "$LAB_STATE/since" ] || return 1
n=$((n + 1))
done
[ "$n" -gt 0 ]
}
task 1 "GRUB menu for 10 seconds, no quiet or splash"
need "the settings read by update-grub give GRUB_TIMEOUT_STYLE=menu" '[ "$(setting GRUB_TIMEOUT_STYLE)" = menu ] || [ -z "$(setting GRUB_TIMEOUT_STYLE)" ]'
need "the settings read by update-grub give GRUB_TIMEOUT=10" '[ "$(setting GRUB_TIMEOUT)" = 10 ]'
need "the settings have no quiet or splash on the command line" '! printf "%s %s" "$(setting GRUB_CMDLINE_LINUX_DEFAULT)" "$(setting GRUB_CMDLINE_LINUX)" | grep -Ewq "quiet|splash"'
need "grub.cfg shows the menu for 10 seconds (update-grub)" 'grep -Eq "^[[:space:]]*set timeout=10$" $cfg && ! grep -Eq "timeout_style=(hidden|countdown)" $cfg'
need "grub.cfg boots the default entry without quiet or splash" '[ -n "$(first_linux)" ] && ! first_linux | grep -Ewq "quiet|splash"'
need "this boot ran without quiet or splash (/proc/cmdline; reboot)" '! grep -Ewq "quiet|splash" /proc/cmdline'
task 2 "Boot into recovery mode and get a writable root shell" self
task 3 "Boot from grub rescue>, then repair GRUB for good" self
task 4 "The initramfs of every installed kernel rebuilt"
need "every /boot/initrd.img-VERSION is newer than the lab's start (update-initramfs)" kernels_rebuilt
@@ -0,0 +1,3 @@
TITLE="3.1 Boot problems: GRUB 2 and the initramfs"
CHAPTER=/ubuntu-desktop/3-installing/3-1-boot-problems/
SYSTEMS="debian"
@@ -0,0 +1,19 @@
# GRUB's files back where task 3 found them.
[ ! -e /boot/grub ] && [ -d /boot/grub.bak ] && mv /boot/grub.bak /boot/grub
rm -f /root/recovery-test
if [ -f "$LAB_STATE/default/grub" ]; then
changed=
cmp -s "$LAB_STATE/default/grub" /etc/default/grub || changed=1
if [ -d "$LAB_STATE/default/grub.d" ]; then
diff -r "$LAB_STATE/default/grub.d" /etc/default/grub.d >/dev/null 2>&1 || changed=1
elif [ -e /etc/default/grub.d ]; then
changed=1
fi
if [ -n "$changed" ]; then
cp -a "$LAB_STATE/default/grub" /etc/default/grub
rm -rf /etc/default/grub.d
[ -d "$LAB_STATE/default/grub.d" ] && cp -a "$LAB_STATE/default/grub.d" /etc/default/grub.d
update-grub >/dev/null 2>&1
fi
fi
true
@@ -0,0 +1,10 @@
command -v update-grub >/dev/null || refuse "this lab needs GRUB 2 with update-grub"
[ -f /boot/grub/grub.cfg ] || refuse "/boot/grub/grub.cfg is missing; this lab needs a system that boots with GRUB 2 from /boot/grub"
[ -e /boot/grub.bak ] && refuse "/boot/grub.bak exists; put it back or remove it first"
command -v update-initramfs >/dev/null || refuse "this lab needs initramfs-tools (update-initramfs)"
mkdir -p "$LAB_STATE/default"
cp -a /etc/default/grub "$LAB_STATE/default/"
[ -d /etc/default/grub.d ] && cp -a /etc/default/grub.d "$LAB_STATE/default/"
# Task 4 looks for initramfs images newer than this.
sleep 1
touch "$LAB_STATE/since"
@@ -0,0 +1,32 @@
# Ubuntu Desktop 3.3: A bootable USB drive
Checking the signature on Ubuntu's checksum file, seeing a changed copy
fail, and writing an ISO image to a drive with `dd`. A spare disk stands
in for the USB drive.
## Before you start
The lab adds an empty spare disk, `/dev/loop40` (64 MiB), and an empty
folder `/root/usb` to work in. The VM needs to reach
`releases.ubuntu.com`. `xorriso` isn't installed.
## Tasks
1. In `/root/usb`, download `SHA256SUMS` and `SHA256SUMS.gpg` for Ubuntu
24.04 (`wget https://releases.ubuntu.com/24.04/SHA256SUMS
https://releases.ubuntu.com/24.04/SHA256SUMS.gpg`) and check the
signature.
2. Copy `SHA256SUMS` to `/root/usb/SHA256SUMS.copy`, change one character
of the copy, and check its signature again.
3. Make a small ISO with the volume label `TEST` (install `xorriso`, then
`xorriso -as mkisofs -V TEST -o test.iso SOMEDIR`) and write it to the
spare disk `/dev/loop40` with `dd`.
## Hints
- `gpgv --keyring /usr/share/keyrings/ubuntu-archive-keyring.gpg
SHA256SUMS.gpg SHA256SUMS` should print `Good signature`.
- Change a single letter or digit, for example with `sed -i '1s/^./0/'`
or in an editor; gpgv should now print `BAD signature`.
- `dd if=test.iso of=/dev/loop40 bs=4M conv=fsync`, then `blkid
/dev/loop40` shows `LABEL="TEST"` and `TYPE="iso9660"`.
@@ -0,0 +1,17 @@
K=/usr/share/keyrings/ubuntu-archive-keyring.gpg
U=/root/usb
task 1 "SHA256SUMS downloaded and its signature good"
need "/root/usb/SHA256SUMS and SHA256SUMS.gpg exist" '[ -s $U/SHA256SUMS ] && [ -s $U/SHA256SUMS.gpg ]'
need "SHA256SUMS lists the 24.04 images" 'grep -q "ubuntu-24\.04.*\.iso" $U/SHA256SUMS'
need "gpgv says the signature is good" 'gpgv --keyring $K $U/SHA256SUMS.gpg $U/SHA256SUMS'
task 2 "A copy with one character changed fails the check"
need "/root/usb/SHA256SUMS.copy exists" '[ -f $U/SHA256SUMS.copy ]'
need "it differs from SHA256SUMS in exactly one character" '[ "$(stat -c %s $U/SHA256SUMS.copy)" = "$(stat -c %s $U/SHA256SUMS)" ] && [ "$(cmp -l $U/SHA256SUMS $U/SHA256SUMS.copy | wc -l)" = 1 ]'
need "gpgv reports a bad signature for it" 'gpgv --keyring $K $U/SHA256SUMS.gpg $U/SHA256SUMS.copy 2>&1 | grep -q "BAD signature"'
task 3 "An ISO labeled TEST written to /dev/loop40"
need "xorriso is installed" 'command -v xorriso'
need "/dev/loop40 holds an ISO 9660 filesystem" '[ "$(blkid -p -s TYPE -o value /dev/loop40)" = iso9660 ]'
need "its label is TEST" '[ "$(blkid -p -s LABEL -o value /dev/loop40)" = TEST ]'
@@ -0,0 +1,3 @@
TITLE="3.3 A bootable USB drive"
CHAPTER=/ubuntu-desktop/3-installing/3-3-bootable-usb/
SYSTEMS="debian"
@@ -0,0 +1,8 @@
installed_pkgs() { dpkg-query -W -f '${db:Status-Abbrev} ${Package}\n' 2>/dev/null | awk '$1 ~ /^.i/ {print $2}' | sort -u; }
rm -rf /root/usb /root/test.iso
if [ -z "$(state_get had-xorriso)" ] && [ -f "$LAB_STATE/pkgs-before" ]; then
# xorriso and the libraries it brought in.
added=$(comm -13 "$LAB_STATE/pkgs-before" <(installed_pkgs) | grep -E '^(xorriso|libburn|libisofs|libisoburn|libjte)' || true)
[ -n "$added" ] && DEBIAN_FRONTEND=noninteractive apt-get -y -qq purge $added >/dev/null 2>&1
fi
true
@@ -0,0 +1,8 @@
installed_pkgs() { dpkg-query -W -f '${db:Status-Abbrev} ${Package}\n' 2>/dev/null | awk '$1 ~ /^.i/ {print $2}' | sort -u; }
[ -e /root/usb ] && refuse "/root/usb already exists; move it away first"
command -v gpgv >/dev/null || pkg_install gpgv
[ -f /usr/share/keyrings/ubuntu-archive-keyring.gpg ] || refuse "this lab needs /usr/share/keyrings/ubuntu-archive-keyring.gpg (package ubuntu-keyring)"
pkg_installed xorriso && state_set had-xorriso 1
installed_pkgs >"$LAB_STATE/pkgs-before"
disk_add 0 64M >/dev/null
mkdir /root/usb
@@ -0,0 +1,32 @@
# Ubuntu Desktop 3.4: Proprietary GPU and Wi-Fi drivers
Finding out whether a machine needs a proprietary driver, where those
drivers live in the archive, the Secure Boot state, and which kernel
driver each network device uses. Nothing gets installed: the lab runs
on a VM, which has no hardware that needs these drivers.
## Before you start
The lab installs `ubuntu-drivers-common` (the `ubuntu-drivers` command)
if it's missing; reset removes it again. Write your answers to the files
in `/root/lab-answers/`, one item per line.
## Tasks
1. Find out whether this machine needs any proprietary driver. Write
`yes` or `no` to `/root/lab-answers/1`.
2. Find which component the NVIDIA 580 driver (`nvidia-driver-580`) and
the Broadcom STA Wi-Fi driver (`bcmwl-kernel-source`) are in, and what
Secure Boot state this machine is in. Write the two components on
lines 1 and 2 of `/root/lab-answers/2`, and on line 3 `enabled`,
`disabled` or `no EFI`.
3. For each network device, find the kernel driver in use. Write the
drivers to `/root/lab-answers/3`, one line per network device.
## Hints
- `ubuntu-drivers devices` prints nothing when every device works with
open source drivers.
- `apt-cache show PACKAGE | grep -m1 Section`; `mokutil --sb-state`.
- `lspci -k` shows a `Kernel driver in use:` line under each network
controller.
@@ -0,0 +1,31 @@
line() { sed -n "${2}p" "/root/lab-answers/$1" 2>/dev/null | sed 's/^[[:space:]]*//; s/[[:space:]]*$//' | tr A-Z a-z; }
component() { local s; s=$(apt-cache show "$1" 2>/dev/null | awk '$1 == "Section:" {print $2; exit}'); [ -n "$s" ] || return; case $s in */*) printf '%s' "${s%%/*}" ;; *) printf main ;; esac; }
needs_driver() { [ -n "$(timeout 120 ubuntu-drivers devices 2>/dev/null | grep -v '^[[:space:]]*$')" ]; }
sb_ok() {
local a; a=$(line 2 3); s=$(mokutil --sb-state 2>&1)
case $s in
*"SecureBoot enabled"*) [ "$a" = enabled ] ;;
*"SecureBoot disabled"*) [ "$a" = disabled ] ;;
*) [[ $a =~ (no|not|bios|legacy|unsupported|efi) ]] && [ "$a" != enabled ] && [ "$a" != disabled ] ;;
esac
}
# PCI network controllers and the driver each uses, as lspci -k shows it.
net_drivers() { lspci -k 2>/dev/null | awk '/^[^[:space:]]/ {net = ($0 ~ /(Ethernet|Network) controller/)} net && /Kernel driver in use:/ {print $NF}'; }
# Accepted names: lspci's, and the network interfaces' own drivers.
ok_drivers() {
net_drivers
for d in /sys/class/net/*/device/driver; do [ -e "$d" ] && basename "$(readlink -f "$d")"; done
}
given() { grep -v '^[[:space:]]*$' /root/lab-answers/3 2>/dev/null | sed 's/^[[:space:]]*//; s/[[:space:]]*$//; s/.*:[[:space:]]*//'; }
task 1 "Whether this machine needs a proprietary driver"
need "/root/lab-answers/1 says yes or no, matching ubuntu-drivers devices" 'a=$(line 1 1); if needs_driver; then [ "$a" = yes ]; else [ "$a" = no ]; fi'
task 2 "Components of the NVIDIA and Broadcom drivers, Secure Boot state"
need "line 1 of /root/lab-answers/2 is nvidia-driver-580's component" 'c=$(component nvidia-driver-580); [ -n "$c" ] && [ "$(line 2 1)" = "$c" ]'
need "line 2 is bcmwl-kernel-source's component" 'c=$(component bcmwl-kernel-source); [ -n "$c" ] && [ "$(line 2 2)" = "$c" ]'
need "line 3 is this machine's Secure Boot state (mokutil --sb-state)" sb_ok
task 3 "Kernel driver of each network device"
need "/root/lab-answers/3 has a line for each network device" '[ "$(given | wc -l)" -ge "$(net_drivers | wc -l)" ] && [ "$(given | wc -l)" -ge 1 ]'
need "every line is a network device's driver in use" '[ -z "$(comm -23 <(given | sort -u) <(ok_drivers | sort -u))" ]'
@@ -0,0 +1,3 @@
TITLE="3.4 Proprietary GPU and Wi-Fi drivers"
CHAPTER=/ubuntu-desktop/3-installing/3-4-proprietary-drivers/
SYSTEMS="debian"
@@ -0,0 +1,4 @@
if [ -s "$LAB_STATE/pkgs-added" ]; then
DEBIAN_FRONTEND=noninteractive apt-get -y -qq purge $(cat "$LAB_STATE/pkgs-added") >/dev/null 2>&1
fi
true
@@ -0,0 +1,6 @@
installed_pkgs() { dpkg-query -W -f '${db:Status-Abbrev} ${Package}\n' 2>/dev/null | awk '$1 ~ /^.i/ {print $2}' | sort -u; }
if ! command -v ubuntu-drivers >/dev/null; then
installed_pkgs >"$LAB_STATE/pkgs-before"
pkg_install ubuntu-drivers-common
comm -13 "$LAB_STATE/pkgs-before" <(installed_pkgs) >"$LAB_STATE/pkgs-added"
fi
+31
View File
@@ -0,0 +1,31 @@
# Ubuntu Desktop 3.5: LVM volumes, filesystems and snapshots
A volume group on a spare disk, an ext4 logical volume mounted at boot,
growing it onto a second disk while it's mounted, and getting a deleted
file back by merging a snapshot.
## Before you start
The lab adds two empty spare disks: `/dev/loop40` (1 GiB, the first) and
`/dev/loop41` (2 GiB, the second). They come back at every boot. The LVM
tools are installed.
## Tasks
1. Make a volume group `vg1` from the first spare disk and an 800 MiB
logical volume `home2` with ext4, mounted at `/mnt/home2` at every
boot.
2. Grow `home2` by 1.5 GiB, adding the second disk to the group first if
needed, without unmounting it.
3. Create a file `/mnt/home2/notes.txt`, snapshot `home2`, delete
`notes.txt`, and get it back by merging the snapshot.
## Hints
- `pvcreate`, `vgcreate`, `lvcreate -L 800M -n`, `mkfs.ext4`; an
`/etc/fstab` line for `/dev/mapper/vg1-home2` (or its UUID), then
`systemctl daemon-reload` and `mount -a`.
- `vgs vg1` shows the free space; `vgextend`, then `lvextend -r -L +1.5G`
grows the filesystem too. `df -h /mnt/home2` should show about 2.3G.
- `lvcreate -s -L 200M -n NAME vg1/home2`; unmount, `lvconvert --merge
vg1/NAME`, mount again. `lvs vg1` no longer lists the snapshot.
Loaded 100 of 195 files, more files were not shown because too many files have changed in this diff. Show more