Hands-on Linux security & DevSecOps training, driven by the
dsoxlab CLI. This repository is the
lab catalog behind the Linux track of
blog.stephane-robert.info,
geared toward the RHCSA (EX200 v10) and LFCS certifications with a
systematic hardening angle.
linux-dsoxlab-training is a content repository, not an application. It
provides:
- guided labs with precise instructions,
- challenges with no step-by-step, to check autonomy,
- capstones that synthesize a full block,
- automated validation that proves the state of the system (not that a command was typed),
- scoring with cost-weighted hints.
The dsoxlab CLI is the single entry point: it starts a lab, shows the
instructions, validates, scores, and reports. The CLI lives in its own repo
and is installed separately — it is not part of this repository.
- Python 3.11+ and
uv git- For L2+ (VM labs — systemd, firewall, SELinux, storage): a provider among KVM/libvirt, Incus, or a supported cloud (Outscale). Shell labs (L1) need nothing more than a terminal.
dsoxlab is published on PyPI — install it
as a standalone tool:
# 1. Install the dsoxlab CLI (external tool, stays out of this repo)
uv tool install dsoxlab # or: pipx install dsoxlab
# 2. Clone this lab catalog
git clone https://github.com/stephrobert/linux-dsoxlab-training.git
cd linux-dsoxlab-training
# 3. Check everything is in place
dsoxlab doctorStart with section l1. Its twenty labs run in a plain terminal: no virtual
machine, no provider, nothing to provision. You can do them on your own box,
right now.
dsoxlab use l1 # starting section
dsoxlab next # → l1-discover-linux-mapThen, for this lab and every other one, the same four-step cycle:
dsoxlab course l1-discover-linux-map # 1. the context, then the course
dsoxlab challenge l1-discover-linux-map # 2. what is asked of you
dsoxlab run l1-discover-linux-map # 3. prepares your workspace
# (challenge/work/) and puts you in it
dsoxlab check l1-discover-linux-map # 4. validate and scorerun is the step people skip: it creates the files you work on, and for a VM
lab it puts the machine into the state the scenario describes. A check run
without run fails by reporting that nothing is done — true, but misleading.
Stuck? dsoxlab hint <id> reveals a hint, at the cost of a few points.
Sections l2 and beyond configure real systems: systemd, firewall, SELinux,
storage. They need an active provider and provisioned infrastructure.
dsoxlab use --provider kvm # persisted for this repo
dsoxlab provision # creates the machines (a few minutes)
dsoxlab status # check they answerIf a machine becomes unreachable, see Recovering an unreachable machine.
Check your environment with dsoxlab doctor (Python, pytest, runtimes, detected
labs). This repo declares several infrastructure providers, so VM labs need an
active one:
New labs land in this repository, and the CLI evolves separately. Update each on its own:
git pull # pull new/updated labs into your clone
uv tool upgrade dsoxlab # update the CLI (or: pipx upgrade dsoxlab)Your in-progress answers live in each lab's challenge/work/, which is
gitignored — so git pull brings new labs without ever touching your work.
The catalog is described by data, not code — the dsoxlab engine stays
domain-agnostic and reads two levels of files:
meta.ymlat the repo root declares the repository identity, the infrastructure topology (network, hosts, providers), and the order of sections shown bylist-labs.lab.yamlper lab (underlabs/linux/<section>/<lab>/) declares itsskills,level,runtime(shell/incus/kvm + target host),distros,doc_url, and avalidationblock (functional,security,persistence_after_reboot). An optionallab.fr.yamloverridestitleanddescriptionin French.
dsoxlab validate-structure checks the whole contract: meta.yml is well
formed, every referenced lab exists with a valid lab.yaml, every
runtime.host points to a declared host, and every referenced test/script file
is present.
A learner drives everything through the CLI; a typical run:
dsoxlab use --provider kvm # pick an infra provider (VM labs)
dsoxlab doctor # check the environment (Python, pytest, runtimes, labs)
dsoxlab list-labs # browse the catalog
dsoxlab show <id> # metadata + status of one lab
dsoxlab run <id> # prepare & start the lab environment
dsoxlab course <id> # read the guided course (optional)
dsoxlab challenge <id> # read the mission (no step-by-step)
dsoxlab hint <id> # reveal a hint (deducted from the score)
dsoxlab check <id> # run the tests, compute & record the score
dsoxlab submit <id> # final submission, closes the session
dsoxlab progress # per-block progress, average scorerun is where the environment comes up. For a shell lab it creates the
lab's workdir and copies the declared fixtures. For a VM lab it selects the
target host (Terraform-provisioned) and applies the lab's setup with Ansible.
| Runtime | Backend | What it gives you |
|---|---|---|
shell |
local shell | Fast, single-host exercises (files, text, permissions). No VM cost — tests run on your own machine and validate its real state. |
incus |
Incus containers | Isolated Linux environments, quick to start. |
kvm |
Terraform + libvirt | Full VMs, the only runtime that can prove reboot/persistence (systemd, firewall, SELinux, storage). |
VM labs are provisioned once with dsoxlab provision (Terraform) and torn down
with dsoxlab destroy. Providers (KVM/Incus/Outscale) are interchangeable and
selected per repo with dsoxlab use --provider <name>; IPs are assigned by the
provider, never hard-coded.
Validation proves the state of the system, it does not trust the learner.
Each lab ships pytest / pytest-testinfra tests under
challenge/tests/ that assert facts on the machine: the service is running
and enabled, the mount is present and declared in /etc/fstab by UUID, the
SELinux context is set persistently. A test that merely checks "a command was
typed" is rejected.
- In CI / instructor mode, a root
conftest.pyfixture replays the referencesolution/before the tests, to prove the solution itself is correct. - In
dsoxlab check(the learner path) that replay is disabled (LAB_NO_REPLAY=1) so the tests validate the learner's own work. - Persistence after reboot is a first-class criterion: it is exactly what fails RHCSA candidates, so VM labs that configure something meant to survive a reboot assert it explicitly.
check records a score (passed/total minus the cost of any hints used). Hints
are cost-weighted — revealing one deducts points, which is why they are
opt-in. History lives in a SQLite database local to this repository
(.dsoxlab.db at the root, gitignored); dsoxlab scores and
dsoxlab progress read it. The active session (context, provider) is stored per
repo in .dsoxlab-context.json.
A lab that deliberately breaks SSH, networking or the root mount can leave a VM unreachable, and that is sometimes the point of the exercise. When the machine stops answering and the console is not enough, the reliable route is to rebuild the park:
dsoxlab destroy --yes # about 6 s
dsoxlab provision # about 4 min, all 3 hosts come back readyThe IPs are reassigned identically (10.10.30.11 to .13): the ssh_config
fragment and the inventory are regenerated, nothing else needs touching. Your
progress is unaffected: it lives in .dsoxlab.db, not in the VMs.
dsoxlab destroy --host <fqdn> exists, but it does not recover a single
machine: Terraform also destroys everything that depends on the target, so
asking for one host takes others down with it (measured: 7 resources planned
for a single one requested). Use it only to narrow a plan, never as a recovery
procedure.
Labs live under labs/linux/ and are ordered by meta.yml. The table below is
generated from the real lab.yaml files — run python3 scripts/gen_catalog.py
to refresh it.
| Lab (id) | Title | Level | Certif | Runtime | Companion guide |
|---|---|---|---|---|---|
l1-discover-linux-map |
Map Linux: kernel, distribution and key directories | l1 | RHCSA · LFCS | shell | guide |
l1-choose-distro |
Choose your reference Linux distribution | l1 | RHCSA · LFCS | shell | guide |
l1-prepare-vm |
Identify your Linux machine | l1 | RHCSA · LFCS | shell | guide |
l1-first-terminal |
First steps in the terminal | l1 | RHCSA · LFCS | shell | guide |
l1-read-a-command |
Read and decode a command | l1 | RHCSA · LFCS | shell | guide |
l1-get-help |
Get help from the command line | l1 | RHCSA · LFCS | shell | guide |
l1-linux-filesystem |
Linux filesystem hierarchy (FHS) | l1 | RHCSA · LFCS | shell | guide |
l1-navigate-filesystem |
Navigate the filesystem | l1 | RHCSA · LFCS | shell | guide |
l1-paths-absolute-relative |
Absolute and relative paths | l1 | RHCSA · LFCS | shell | guide |
l1-redirections-pipes |
Redirect streams and chain commands with pipes | l1 | RHCSA | shell | guide |
l1-grep-regex |
Filter a log with grep and regular expressions | l1 | RHCSA · LFCS | shell | guide |
l1-text-processing |
Transform and aggregate text with cut, sort, uniq, sed and awk | l1 | RHCSA · LFCS | shell | guide |
l1-find-files |
Locate files with find by name, size and permissions | l1 | RHCSA · LFCS | shell | guide |
l1-tar-archives |
Archive, compress and selectively extract with tar, gzip and bzip2 | l1 | RHCSA | shell | guide |
l1-permissions-ugo |
Set exact file permissions with chmod (octal and symbolic) | l1 | RHCSA · LFCS | shell | guide |
l1-links-hard-sym |
Create hard and symbolic links and tell them apart | l1 | RHCSA | shell | guide |
l1-bash-script |
Write a first Bash script: variables, a loop and a condition | l1 | RHCSA · LFCS | shell | guide |
l1-git-basics |
Initialize a Git repo: commit, history and a branch | l1 | LFCS | shell | guide |
l1-env-profiles |
Environment variables: export, PATH and a sourced env file | l1 | LFCS | shell | guide |
l1-ssl-certificates |
Inspect a TLS certificate with openssl | l1 | LFCS | shell | guide |
| Lab (id) | Title | Level | Certif | Runtime | Companion guide |
|---|---|---|---|---|---|
l2-swap-management |
Add and manage swap space | l2 | RHCSA · LFCS | vm | guide |
l2-fstab-persist-uuid |
Mount a filesystem persistently by UUID in /etc/fstab | l2 | RHCSA · LFCS | vm | guide |
l2-partition-gpt |
Create GPT partitions on a disk with parted | l2 | RHCSA · LFCS | vm | guide |
l2-filesystem-create-xfs |
Create and label an XFS filesystem, then mount it | l2 | RHCSA · LFCS | vm | guide |
l2-disk-space-troubleshoot |
Diagnose a full filesystem and reclaim space | l2 | RHCSA · LFCS | vm | guide |
l2-storage-performance |
Tune a mount for performance with noatime (persistently) | l2 | LFCS | vm | guide |
l2-lvm-extend-persist |
Extend a logical volume and prove the mount survives a reboot | l2 | RHCSA · LFCS | vm | guide |
l2-nfs-mount-persist |
Mount an NFS export persistently from a server | l2 | LFCS | vm | guide |
l2-autofs-ondemand |
Mount a filesystem on demand with autofs | l2 | LFCS | vm | guide |
l2-raid-mdadm |
Build a software RAID 1 with mdadm | l2 | LFCS | vm | guide |
l2-luks-encryption |
Encrypt a disk with LUKS | l2 | RHCSA | vm | guide |
l2-user-lifecycle |
Create a local account with exact UID, shell and groups | l2 | RHCSA · LFCS | vm | guide |
l2-password-policy |
Enforce password aging and complexity policy | l2 | RHCSA · LFCS | vm | guide |
l2-sudo-delegation |
Delegate limited sudo rights with a sudoers drop-in | l2 | RHCSA · LFCS | vm | guide |
l2-acl-posix |
Grant fine-grained access with POSIX ACLs | l2 | RHCSA · LFCS | vm | guide |
l2-collaborative-setgid |
Set up a collaborative directory with the set-GID bit | l2 | RHCSA · LFCS | vm | guide |
l2-package-management |
Install, remove and query packages with dnf | l2 | RHCSA | vm | guide |
l2-repo-configure |
Configure a dnf repository with a .repo file | l2 | RHCSA | vm | guide |
| Lab (id) | Title | Level | Certif | Runtime | Companion guide |
|---|---|---|---|---|---|
l3-boot-target |
Set the default systemd boot target | l3 | RHCSA · LFCS | vm | guide |
l3-grub-kernel-args |
Add a persistent kernel boot parameter | l3 | RHCSA | vm | guide |
l3-service-create-unit |
Create and enable a systemd service unit | l3 | RHCSA · LFCS | vm | guide |
l3-service-diagnose |
Diagnose and fix a systemd service stuck in a crash loop | l3 | RHCSA · LFCS | vm | guide |
l3-journald-persist |
Make the systemd journal persistent across reboots | l3 | RHCSA · LFCS | vm | guide |
l3-scheduling-cron |
Schedule a recurring job with cron | l3 | RHCSA · LFCS | vm | guide |
l3-scheduling-at |
Schedule a one-shot job with at | l3 | RHCSA · LFCS | vm | guide |
l3-scheduling-timers |
Schedule a recurring job with a systemd timer | l3 | RHCSA · LFCS | vm | guide |
l3-app-constraints |
Set per-user resource limits (open files) with limits.d | l3 | LFCS | vm | guide |
l3-sysctl-persist |
Harden kernel parameters persistently with sysctl.d | l3 | RHCSA | vm | guide |
l3-process-signals-priority |
Lower a service's scheduling priority with Nice | l3 | LFCS | vm | guide |
l3-tuned-profile |
Apply a tuned performance profile | l3 | RHCSA | vm | guide |
l3-fs-readonly-recover |
Recover a read-only mount caused by a broken fstab | l3 | RHCSA · LFCS | vm | guide |
l3-ssh-access-recovery |
Repair a broken sshd config before it locks you out | l3 | RHCSA · LFCS | vm | guide |
| Lab (id) | Title | Level | Certif | Runtime | Companion guide |
|---|---|---|---|---|---|
l4-ntp-sync |
Synchronize the clock with chrony and set the timezone, persistently | l4 | RHCSA | vm | guide |
l4-network-static-persist |
Configure a persistent static IPv4 with NetworkManager | l4 | RHCSA | vm | guide |
l4-network-troubleshoot |
Diagnose and restore a down network connection | l4 | RHCSA | vm | guide |
l4-firewall-persist |
Open a firewalld service permanently | l4 | RHCSA | vm | guide |
l4-ssh-key-auth-harden |
Set up hardened key-based SSH access for a service user | l4 | RHCSA | vm | guide |
l4-podman-basic |
Run a detached container with Podman | l4 | RHCSA | vm | guide |
l4-podman-systemd-persist |
Run a container as a systemd service with Quadlet (boot-persistent) | l4 | RHCSA | vm | guide |
l4-podman-images |
Manage container images: pull, tag, save and inspect | l4 | RHCSA | vm | guide |
l4-selinux-boolean-port |
Allow a service with SELinux: persistent boolean and labeled port | l4 | RHCSA | vm | guide |
l4-selinux-context-fix |
Fix a file's SELinux context, persistently | l4 | RHCSA | vm | guide |
l4-selinux-diagnose-avc |
Diagnose an SELinux denial (AVC) and fix it the right way | l4 | RHCSA | vm | guide |
l4-nat-portforward |
Set up persistent NAT port forwarding with nftables | l4 | RHCSA | vm | guide |
l4-ldap-integration |
Authenticate Linux against an LDAP directory with SSSD | l4 | RHCSA | vm | guide |
l4-reverse-proxy-lb |
Load-balance a web backend with HAProxy | l4 | LFCS | vm | guide |
l4-bridge-bonding |
Aggregate links: an active-backup bond under a bridge with nmcli | l4 | LFCS | vm | guide |
| Lab (id) | Title | Level | Certif | Runtime | Companion guide |
|---|---|---|---|---|---|
lfcs-package-apt |
Manage Debian packages with apt and dpkg | l2 | LFCS | vm | guide |
lfcs-firewall-ufw |
Open a service through the firewall with ufw | l4 | LFCS | vm | guide |
lfcs-apparmor |
Manage an AppArmor profile: switch it to complain mode | l4 | LFCS | vm | guide |
lfcs-netplan-static |
Configure a static IP and route with netplan | l4 | LFCS | vm | guide |
lfcs-storage-quotas |
Enable XFS user quotas and enforce a limit | l2 | LFCS | vm | guide |
lfcs-mount-cifs |
Mount an SMB/CIFS share persistently and safely | l2 | LFCS | vm | guide |
| Lab (id) | Title | Level | Certif | Runtime | Companion guide |
|---|---|---|---|---|---|
drill-essential-commands |
Drill — essential commands under exam conditions | l1 | RHCSA · LFCS | vm | guide |
drill-users-groups |
Drill — users, groups and delegation under exam conditions | l2 | RHCSA · LFCS | vm | guide |
drill-systemd |
Drill — systemd units, timers and scheduling under exam conditions | l3 | RHCSA · LFCS | vm | guide |
drill-storage |
Drill — partitions, LVM and swap under exam conditions | l2 | RHCSA · LFCS | vm | guide |
drill-packages |
Drill — package management under exam conditions | l2 | RHCSA · LFCS | vm | guide |
drill-firewall |
Drill — firewall under exam conditions | l4 | RHCSA · LFCS | vm | guide |
drill-selinux |
Drill — SELinux under exam conditions | l4 | RHCSA | vm | guide |
drill-apparmor |
Drill — AppArmor under exam conditions | l4 | LFCS | vm | guide |
drill-network |
Drill — static networking under exam conditions | l4 | RHCSA · LFCS | vm | guide |
| Lab (id) | Title | Level | Certif | Runtime | Companion guide |
|---|---|---|---|---|---|
rhcsa-mock-exam |
RHCSA EX200 mock exam — 20 tasks across 2 VMs | l2 | RHCSA | vm | guide |
lfcs-mock-exam |
LFCS mock exam — 17 tasks on Ubuntu 24.04 | l2 | LFCS | vm | guide |
84 labs — table générée par scripts/gen_catalog.py.
- Contributions: see CONTRIBUTING.
- Conduct: Code of Conduct · Security: SECURITY.
- Releases: RELEASING (tar.gz bundles, no PyPI).
- License: CC BY 4.0.