Day 36 — Hardening baseline
Day 36 — Hardening baseline
Stage IV · ~4h
Goal: Apply a small, documented hardening module for a lab server: SSH posture, sudo policy, disable unused services, and careful sysctl/kernel defaults—without cargo-culting every CIS line item.
Hardening can lock you out of a remote VM. Keep a console/VNC path or test with nixos-rebuild test / boot generation rollback ready (Day 13).
Why this day exists
Secrets tooling protects credentials. Hardening reduces attack surface and enforces least privilege. Production-shaped labs should not run with:
- Password SSH
- Root SSH login
- Unnecessary network services
- Empty sudo policies that surprise you later
Today builds a module you can import on server roles—not a security theater checklist of 200 toggles.
Theory 1 — Threat model for this lab
Assume:
| Asset | Threat |
|---|---|
| SSH on a public IP | Password spray, key theft |
| Local users | Accidental privilege escalation |
| Services you enable in Stage IV | Extra open ports |
| Laptop multi-boot lab | Lower remote risk, still practice |
Non-goals today: full SELinux policy authoring, corporate MDM, compliance certifications.
Theory 2 — SSH baseline (NixOS)
# modules/common/hardening/ssh.nix
{
services.openssh = {
enable = true;
settings = {
PasswordAuthentication = false;
KbdInteractiveAuthentication = false;
PermitRootLogin = "no";
X11Forwarding = false;
# MaxAuthTries = 4;
};
};
}| Setting | Intent |
|---|---|
| No passwords | Keys only |
| No root login | Use sudo from admin user |
| No X11 forward | Reduce attack surface on servers |
Ensure your user has openssh.authorizedKeys.keys before applying password-off if you ever had passwords.
Theory 3 — sudo and wheel
# modules/common/hardening/sudo.nix
{
security.sudo = {
enable = true;
wheelNeedsPassword = true; # or false for lab convenience — document choice
execWheelOnly = true;
};
# users.users.alice.extraGroups = [ "wheel" ];
}| Policy | Trade-off |
|---|---|
wheelNeedsPassword = true |
Safer; needs password/TTY |
= false |
Convenient lab; weaker if key stolen + local |
Prefer true on anything network-exposed.
Theory 4 — Disable what you do not use
Examples (only if present/enabled):
{
services.avahi.enable = false;
services.printing.enable = false;
# bluetooth on servers:
hardware.bluetooth.enable = false;
}Do not randomly disable things you depend on (NetworkManager on a laptop role). Use roles:
# modules/roles/server.nix
{
imports = [ ../common/hardening ];
# server-specific disables
}# modules/roles/workstation.nix
{
# lighter hardening; keep user conveniences
}Theory 5 — Kernel / sysctl care
NixOS exposes many boot.kernel.sysctl knobs. Apply few, understand each:
{
boot.kernel.sysctl = {
"net.ipv4.conf.all.rp_filter" = 1;
"net.ipv4.tcp_syncookies" = 1;
# Do not paste a 50-line blog dump without reading
};
}Kernel package pinning and hardened profiles exist in the ecosystem; treat full “hardened kernel” as optional advanced work—not required for gate IV.
Theory 6 — Automatic updates? (judgment)
Unattended upgrades on NixOS are a product decision:
- Flake pins mean “latest” is not automatic unless you automate lock bumps + rebuilds
- Auto-reboot policies can surprise
For this book: manual, deliberate updates (Day 24) unless you later build CI-driven update PRs.
Theory 7 — Module shape
modules/common/hardening/
default.nix # imports ssh.nix sudo.nix misc.nix
ssh.nix
sudo.nix
misc.nix
# default.nix
{
imports = [ ./ssh.nix ./sudo.nix ./misc.nix ];
}Import from server role or common—document if workstations share it.
Worked example — Safe application order
- Confirm console access
- Confirm admin key SSH works
nixos-rebuild boot(activate next reboot) ortest
- Open new SSH session before closing old
- Only then
switchif using switch
# keep existing session; test new config in another tty/ssh
sudo nixos-rebuild test --flake .#labLab 1 — Inventory surface
ss -tulpn | sort
systemctl list-unit-files --state=enabled | head -80Journal: what is listening, what you do not recognize.
Lab 2 — Write hardening module
Create modules/common/hardening/ with SSH + sudo + 2–3 sensible disables for your role.
Lab 3 — Apply carefully
sudo nixos-rebuild test --flake .#lab
# new ssh session
ssh lab
sudo -vThen switch if satisfied.
Lab 4 — Lockout recovery drill (document)
Write exact steps to recover if SSH rejects you:
- Hypervisor console
- Boot previous generation
nixos-rebuild switch --rollbackfrom console
Do not skip writing this.
Lab 5 — Assertions (optional polish)
{ config, lib, ... }:
{
assertions = [
{
assertion = config.services.openssh.settings.PasswordAuthentication == false;
message = "PasswordAuthentication must be false on this host role";
}
];
}Lab 6 — Diff and explain
# compare pre/post listening ports
ss -tulpnExplain each remaining listener in one line (sshd, etc.).
Common gotchas
| Symptom / mistake | What to do |
|---|---|
| Locked out via SSH | Console + previous generation |
| PasswordAuth off before keys | Fix via console; add keys |
| Hardening module on laptop breaks UX | Split server/workstation roles |
| Blind sysctl paste | Remove unknowns |
PermitRootLogin yes “for debug” left on |
Remove before network exposure |
| No recovery notes | Write Lab 4 before remote travel |
Checkpoint
- Hardening module exists and is imported on the lab role
- Password SSH off; root SSH off
- sudo policy explicit
- Unnecessary services disabled with reasons
- Recovery path documented
- Rebuild + new SSH session verified
Commit
git add .
git commit -m "day36: hardening baseline module"Write three personal gotchas before continuing.
Tomorrow
Day 37 — Firewall discipline: deny-by-default posture, per-service ports, and an audit of what is actually open.