Skip to content

Commit 1dec3aa

Browse files
committed
Align docs with profiles and add Mermaid flow diagrams.
Document lab/prod overlays, anti-lockout SSH and MFA rollout visually, and point chapters at matching Ansible tags where useful.
1 parent 6ac3d68 commit 1dec3aa

9 files changed

Lines changed: 136 additions & 47 deletions

File tree

CONTRIBUTING.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Thanks for improving **Linux Server Hardening**.
99
3. **Prefer drop-in files** (`sshd_config.d`, `sysctl.d`, `cron.d`) over rewriting entire vendor configs.
1010
4. **Debian 12/13 first.** Note if a change is Ubuntu-specific.
1111
5. **No lock-out recipes.** SSH and firewall changes must document console recovery.
12+
6. **Diagrams sparingly.** Prefer Mermaid only for multi-step flows (bootstrap/harden, SSH cutover, MFA rollout). Single-control chapters stay Threat/Do/Why text.
1213

1314
## Pull requests
1415

README.md

Lines changed: 37 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -25,46 +25,55 @@ It is **not** a compliance product, a CIS auditor replacement, or a substitute f
2525

2626
## Mental model: six defense layers
2727

28-
```text
29-
┌─────────────────────────────────────────────────────────┐
30-
│ 6. Operations monitoring, mail alerts, re-audit │
31-
├─────────────────────────────────────────────────────────┤
32-
│ 5. Detection auditd, scanners, integrity checks │
33-
├─────────────────────────────────────────────────────────┤
34-
│ 4. Host baseline packages, passwords, auto-updates │
35-
├─────────────────────────────────────────────────────────┤
36-
│ 3. Network edge firewall, rate limits, IDS hooks │
37-
├─────────────────────────────────────────────────────────┤
38-
│ 2. Access control SSH keys, groups, sudo/su policy │
39-
├─────────────────────────────────────────────────────────┤
40-
│ 1. Foundations threat model, OS choice, lab safety │
41-
└─────────────────────────────────────────────────────────┘
28+
Work **bottom-up**. Changing SSH before you have a firewall recovery path is a common lock-out pattern.
29+
30+
```mermaid
31+
flowchart TB
32+
L1["1. Foundations<br/>threat model · OS · lab safety"]
33+
L2["2. Access control<br/>users · SSH keys · sudo/su"]
34+
L3["3. Network edge<br/>UFW · Fail2Ban · PSAD"]
35+
L4["4. Host baseline<br/>NTP · updates · passwords · sysctl"]
36+
L5["5. Detection<br/>auditd · AIDE · scanners"]
37+
L6["6. Operations<br/>mail · logwatch · re-audit"]
38+
L1 --> L2 --> L3 --> L4 --> L5 --> L6
4239
```
4340

44-
Work bottom-up. Changing SSH before you have a firewall recovery path is a common lock-out pattern; the guide orders steps to reduce that risk.
41+
## Automation flow
42+
43+
Always pick a profile: **lab** (disposable) or **prod** (real `ignoreip`, strict ops).
44+
45+
```mermaid
46+
flowchart LR
47+
A["01-bootstrap.yml<br/>inventory: root"] --> B["Verify key login"]
48+
B --> C["02-harden.yml<br/>inventory: admin + new port"]
49+
C --> D["03-audit.yml<br/>Lynis report"]
50+
C -.-> P1["-e @profiles/lab.yml"]
51+
C -.-> P2["-e @profiles/prod.yml"]
52+
```
4553

4654
## Repository layout
4755

4856
```text
4957
docs/ Didactic guide (read in order)
50-
ansible/ Automation that mirrors the layers
51-
playbooks/ Entry points (bootstrap → harden → audit)
52-
roles/ One concern per role
53-
inventories/lab/ Example inventory (replace with yours)
54-
group_vars/ Non-secret defaults + vault placeholders
58+
ansible/
59+
playbooks/ bootstrap → harden → audit
60+
roles/ one concern per role
61+
inventories/lab/ bootstrap vs runtime host files
62+
group_vars/ non-secret defaults + vault placeholders
63+
profiles/ lab.yml / prod.yml overlays
5564
```
5665

5766
| Path | Purpose |
5867
|------|---------|
5968
| [docs/00-start-here.md](docs/00-start-here.md) | How to use the kit safely |
6069
| [docs/01-foundations/](docs/01-foundations/) | Threat model, OS choice, lab safety |
61-
| [docs/02-access-control/](docs/02-access-control/) | Users, SSH (incl. crypto + MFA), privilege boundaries |
70+
| [docs/02-access-control/](docs/02-access-control/) | Users, SSH (crypto + MFA), privileges |
6271
| [docs/03-network/](docs/03-network/) | Firewall, IDS, Docker caveats, CrowdSec |
6372
| [docs/04-host-baseline/](docs/04-host-baseline/) | NTP, updates, passwords, kernel knobs |
6473
| [docs/05-detection/](docs/05-detection/) | auditd, AIDE, malware/rootkit checks |
6574
| [docs/06-operations/](docs/06-operations/) | Mail, logwatch, day-2 review |
66-
| [docs/07-advanced/](docs/07-advanced/) | GRUB, umask, hidepid, Firejail, and more |
67-
| [docs/CONTROL-COVERAGE.md](docs/CONTROL-COVERAGE.md) | What is documented vs automated |
75+
| [docs/07-advanced/](docs/07-advanced/) | AppArmor, FIDO2, GRUB, umask, more |
76+
| [docs/CONTROL-COVERAGE.md](docs/CONTROL-COVERAGE.md) | Documented vs automated map |
6877
| [ansible/README.md](ansible/README.md) | How to run the playbooks |
6978

7079
## Quick start (automation)
@@ -113,8 +122,6 @@ ansible-playbook -i inventories/lab/hosts.yml playbooks/02-harden.yml \
113122

114123
## Quick start (manual learning path)
115124

116-
If you prefer to learn by hand before automating:
117-
118125
1. Read [docs/00-start-here.md](docs/00-start-here.md)
119126
2. Complete [docs/01-foundations/](docs/01-foundations/)
120127
3. Follow layers 2 → 6 in order
@@ -124,12 +131,13 @@ If you prefer to learn by hand before automating:
124131

125132
| Choice | Reason |
126133
|--------|--------|
127-
| Debian-focused examples | Validated on Debian 12/13 (asserted in plays) |
134+
| Debian 12/13 only (asserted) | Predictable packaging and systemd behaviour |
128135
| Ansible Vault for secrets | Passwords and SMTP tokens must not live in git history |
129-
| Safer production defaults | No NOPASSWD, no auto-reboot, no PSAD auto-block unless opted in |
130-
| Default-deny firewall (in and out) | Limits both inbound scanners and unexpected C2 egress |
136+
| Lab / prod profiles | Same roles; different lockout and strictness knobs |
137+
| Fail2Ban ignoreip required by default | Prevents banning your own admin IP on public hosts |
138+
| No NOPASSWD / auto-reboot / PSAD AUTO_IDS by default | Safer production posture |
139+
| Default-deny firewall (in and out) | Limits scanners and unexpected C2 egress |
131140
| Key-only SSH, no root login | Removes the highest-value, most-probed credentials |
132-
| Security-only unattended upgrades | Patches critical CVEs without surprise feature bumps |
133141
| Separate bootstrap vs runtime inventories | Avoids `ansible_user: root` sticking on harden plays |
134142

135143
## Safety contract

ansible/README.md

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,29 @@ ansible-vault encrypt group_vars/all/vault.yml
2727

2828
## Plays
2929

30+
```mermaid
31+
flowchart TB
32+
subgraph bootstrap["Bootstrap (root inventory)"]
33+
B1[admin_user + groups + key + sudo]
34+
end
35+
subgraph harden["Harden (admin inventory + port)"]
36+
H1[packages · ntp · sysctl · ssh]
37+
H2[passwords · updates · firewall/IDS]
38+
H3[mail · scanners · auditd · logwatch]
39+
end
40+
subgraph audit["Audit"]
41+
A1[Lynis force run + optional mail]
42+
end
43+
bootstrap --> harden --> audit
44+
harden -.-> Lab["-e @profiles/lab.yml"]
45+
harden -.-> Prod["-e @profiles/prod.yml"]
46+
```
47+
3048
| Playbook | Inventory | Purpose |
3149
|----------|-----------|---------|
3250
| `01-bootstrap.yml` | `hosts.bootstrap.yml` (root) | Admin user, groups, key, sudo |
3351
| `02-harden.yml` | `hosts.yml` (admin + port) | Baseline hardening |
34-
| `03-audit.yml` | `hosts.yml` | Lynis report (forces audit run) |
52+
| `03-audit.yml` | `hosts.yml` | Lynis report (forces audit run; vault optional) |
3553

3654
## Safer defaults
3755

@@ -47,6 +65,14 @@ ansible-playbook -i inventories/lab/hosts.yml playbooks/02-harden.yml \
4765
--ask-vault-pass --ask-become-pass -e @profiles/prod.yml
4866
```
4967

68+
| Knob | Default (`vars.yml`) | Lab profile | Prod profile |
69+
|------|----------------------|-------------|--------------|
70+
| `harden_fail2ban_require_ignoreip` | `true` | `false` | `true` |
71+
| `harden_strict_ops` | `true` | `false` | `true` |
72+
| `harden_passwordless_sudo` | `false` | `true` | `false` |
73+
| `harden_auto_reboot` | `false` | `true` | `false` |
74+
| `harden_ssh_keys_exclusive` | `false` || `true` |
75+
5076
## Tags
5177

5278
`packages`, `ntp`, `sysctl`, `ssh`, `mfa`, `passwords`, `updates`, `firewall`, `ids`, `mail`, `malware`, `integrity`, `chkrootkit`, `aide`, `auditd`, `logwatch`, `lynis`.
@@ -55,7 +81,7 @@ ansible-playbook -i inventories/lab/hosts.yml playbooks/02-harden.yml \
5581

5682
`--check` is best-effort. Tasks that shell out (moduli trim, aideinit, lynis, psad signature update, test mail) are not fully check-safe.
5783

58-
Optional smoke test (Docker required):
84+
Optional smoke test (Docker required; also runs in CI):
5985

6086
```bash
6187
cd ansible
@@ -72,4 +98,6 @@ roles/
7298
firewall_stack/ outbound_mail/
7399
malware_scan/ integrity_checks/ rootkit_extra/ file_integrity/
74100
audit_framework/ log_digest/ security_audit/
101+
profiles/
102+
lab.yml prod.yml
75103
```

docs/00-start-here.md

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -19,41 +19,53 @@ This kit teaches **why** Linux server controls exist, then helps you apply them
1919
| Verify | How do I prove it worked? |
2020
| Rollback | How do I undo without rebuilding? |
2121

22-
Copy that pattern when you add controls of your own.
22+
Where automation exists, chapters mention the matching Ansible **tags** / roles.
2323

2424
## Suggested order
2525

26-
```text
27-
Foundations → Access → Network → Host baseline → Detection → Operations
26+
```mermaid
27+
flowchart LR
28+
F[Foundations] --> A[Access]
29+
A --> N[Network]
30+
N --> H[Host baseline]
31+
H --> D[Detection]
32+
D --> O[Operations]
2833
```
2934

30-
Skipping ahead is fine for experienced operators, but **never** change the SSH port until:
35+
Skipping ahead is fine for experienced operators, but **never** change the SSH port until the anti-lockout path below is true.
3136

32-
- your admin user exists,
33-
- your public key works,
34-
- the firewall already allows the **new** port,
35-
- a console session is open.
37+
```mermaid
38+
flowchart TD
39+
A[Admin user + SSH key works] --> B[Console / VNC open]
40+
B --> C[UFW allows NEW SSH port]
41+
C --> D[Change sshd + reload]
42+
D --> E[Second SSH session succeeds]
43+
E --> F[Close first session]
44+
```
3645

3746
## Lab vs production
3847

39-
| Topic | Lab | Production |
40-
|-------|-----|------------|
41-
| Secrets in Vault | Optional (still good practice) | Mandatory |
48+
| Topic | Lab (`profiles/lab.yml`) | Production (`profiles/prod.yml`) |
49+
|-------|--------------------------|----------------------------------|
50+
| Secrets in Vault | Recommended | Mandatory |
51+
| Fail2Ban `ignoreip` | Optional | **Required** (real CIDRs, not TEST-NET) |
52+
| `harden_strict_ops` | Off (soft-fail OK) | On (mail/PSAD/Lynis must work) |
53+
| Passwordless sudo | Allowed | Off |
54+
| Auto-reboot after security updates | Allowed | Off / windowed |
55+
| ClamAV / AIDE / chkrootkit | Often on | Opt-in by load |
4256
| Default-deny egress | Recommended | Required if you can list needed ports |
43-
| Mail alerts | Use a throwaway inbox | Use a monitored inbox / ticket route |
44-
| Scanners (ClamAV, rkhunter) | Nightly is fine | Schedule by load; tune false positives |
45-
| Reboots after security updates | Automatic OK | Windowed / approved |
4657

4758
## What “good enough” looks like after day one
4859

4960
- Root cannot SSH in
5061
- Password SSH is off; keys only
5162
- Only members of an SSH allow-group can connect
5263
- Firewall denies unexpected inbound traffic
64+
- Fail2Ban has an admin/VPN allowlist (prod)
5365
- Security updates install without waiting for you
5466
- You receive at least one test alert email
5567

56-
Then iterate: detection depth, kernel hardening, application jails, and continuous audit.
68+
Then iterate: detection depth, MFA enforce, kernel hardening, and continuous audit.
5769

5870
## Next
5971

docs/01-foundations/lab-setup.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,16 @@ ansible_user: admin
5050
ansible_port: 2222
5151
```
5252
53+
Always pass a profile overlay when hardening:
54+
55+
```bash
56+
# Disposable lab
57+
-e @profiles/lab.yml
58+
59+
# Real host (edit ignoreip in profiles/prod.yml first)
60+
-e @profiles/prod.yml
61+
```
62+
5363
## Why
5464

5565
Separating **control node** and **target** matches how you will operate later: automation runs from CI or an admin workstation, not from the box being locked down.
@@ -60,7 +70,8 @@ Snapshots and consoles turn irreversible mistakes into five-minute recoveries, w
6070

6171
```bash
6272
ssh -i ~/.ssh/lab_ed25519 root@SERVER_IP 'uname -a && cat /etc/os-release | head -3'
63-
ansible -i inventories/lab/hosts.yml lab-debian -m ping --key-file ~/.ssh/lab_ed25519
73+
# After copying hosts.bootstrap.yml with your IP:
74+
ansible -i inventories/lab/hosts.bootstrap.yml lab-debian -m ping --key-file ~/.ssh/lab_ed25519
6475
```
6576

6677
You want `pong` and a Debian version string.

docs/02-access-control/ssh-mfa.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,16 @@ A stolen laptop with an unlocked SSH private key (or an exported key without a p
88

99
Complete **key-only SSH** first. Never enable MFA in the same change window as a port or firewall cutover.
1010

11+
```mermaid
12+
stateDiagram-v2
13+
[*] --> KeyOnly: SSH keys work
14+
KeyOnly --> Wired: harden_enable_mfa_role=true<br/>nullok=true (not enforced)
15+
Wired --> Enrolled: each admin runs google-authenticator
16+
Enrolled --> Enforced: nullok=false<br/>harden_ssh_mfa_enable=true
17+
note right of Wired: MFA installed but skippable
18+
note right of Enforced: key + TOTP required
19+
```
20+
1121
```bash
1222
apt install -y libpam-google-authenticator
1323

docs/02-access-control/ssh-service.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,27 @@ Default OpenSSH on port 22 with password authentication is the most scanned surf
88

99
### Order of operations (critical)
1010

11+
```mermaid
12+
sequenceDiagram
13+
participant You
14+
participant SSH as sshd
15+
participant FW as UFW
16+
You->>SSH: 1. Key login works on CURRENT port
17+
You->>FW: 2. Allow NEW SSH port (limit)
18+
You->>SSH: 3. Write drop-in + sshd -t
19+
You->>SSH: 4. Reload sshd
20+
You->>SSH: 5. Open SECOND session on new port
21+
Note over You: Only then close the first session
22+
```
23+
1124
1. Confirm key login as `admin` works on the **current** port.
1225
2. Allow the **new** port in the firewall.
1326
3. Change `sshd` settings.
1427
4. Reload `sshd`.
1528
5. Open a **second** SSH session before closing the first.
1629

30+
Ansible: role `ssh_hardening` (tag `ssh`) pre-allows the port in UFW and flushes handlers before reloading sshd.
31+
1732
### Recommended policy (modern OpenSSH)
1833

1934
Create a drop-in file (cleaner than editing the whole `sshd_config`):

docs/03-network/firewall.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ ufw --force enable
3030
ufw status verbose
3131
```
3232

33+
Ansible: role `firewall_stack` (tags `firewall`, `ids`) — also wires Fail2Ban, PSAD, and dedicated iptables logs. Pair with `-e @profiles/lab.yml` or `prod.yml`.
34+
3335
### Rate limiting
3436

3537
`ufw limit` uses iptables recent-match style limiting on new TCP connections. It will not stop a distributed slow scan, but it blunts simple floods against SSH.

docs/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,5 @@ Read in order for the full learning path.
3737
Coverage checklist: [CONTROL-COVERAGE.md](CONTROL-COVERAGE.md)
3838

3939
Automation: [../ansible/README.md](../ansible/README.md)
40+
41+
Diagrams (Mermaid) appear where order matters: kit layers and playbook flow in the [root README](../README.md), anti-lockout SSH path here and in [ssh-service](02-access-control/ssh-service.md), MFA rollout in [ssh-mfa](02-access-control/ssh-mfa.md).

0 commit comments

Comments
 (0)