Hardened baseline for mixed client fleets: one tested role library, per-tenant inventories, applied in rolling batches.
  • Shell 63.1%
  • Python 12.6%
  • PowerShell 12.5%
  • Jinja 7%
  • Makefile 1.9%
  • Other 2.9%
Find a file
Motoko Kusanagi 2dc352ce14
fix(docker-engine): install python3-debian first
deb822_repository needs the Python debian library on the target, and
nothing installed it: install mode failed at the repository task on a
host without python3-debian, under --check and in a real run. Install
it with apt first; under --check, where it cannot be installed yet,
skip the repository with a notice so the rest of the run previews.
2026-10-22 23:20:14 +02:00
.agents
.claude
.codex
.config/molecule
.opencode
docs feat(clustered): label swarm logs at 50m x 5 2026-10-22 22:57:20 +02:00
group_vars feat(clustered): label swarm logs at 50m x 5 2026-10-22 22:57:20 +02:00
plugins/cache
roles fix(docker-engine): install python3-debian first 2026-10-22 23:20:14 +02:00
scripts
tests fix(docker-engine): install python3-debian first 2026-10-22 23:20:14 +02:00
.ansible-lint
.gitignore
.prettierrc
.yamllint
AGENTS.md
ansible.cfg
CONTRIBUTING.md fix(docker-engine): install python3-debian first 2026-10-22 23:20:14 +02:00
COPYING
fortify.yml
Makefile feat(swarm): install jq on every swarm node 2026-10-22 22:55:51 +02:00
os-update.yml
reachable.yml
README.md
requirements-dev.txt
requirements.yml
ssh-copy-id.yml

castrum

Hardened baseline for mixed client fleets: one tested role library, per-tenant inventories, applied in rolling batches.

A Roman castrum was built to the same plan wherever the legion stopped — same layout, same gates, same defences, on any ground. This repository is that plan for a fleet of machines: the same hardening, firewall and service configuration applied identically across every site it manages, varied only where the ground demands it.

Each site's inventory lives outside this repository. What is here is what a group means, the roles that implement it, and the tests that prove they do.

Goal

castrum gives every host in a fleet the same hardening, firewall, services, sysadmin tooling and updates, varied only by the groups a host belongs to and its own host variables, never by editing a playbook. fortify.yml and os-update.yml apply it in widening batches, one host first, so a bad change breaks one machine while the rest are still untouched.

Scope is GNU/Linux, Debian family, plus Windows 11 workgroup PCs over OpenSSH: their hostname, a hardening baseline, the Windows firewall, Windows Update and, in scan_shares, a scanner drop share. A Windows Server gets the Windows firewall, key-only SSH and Windows Update. castrum is not a provisioning tool — it configures machines that already exist — and not a multi-OS abstraction.

Quick start

  1. Clone the repository and build the toolchain (needs Python 3.12+):

    git clone https://git.unitoo.it/claudiomaradonna/castrum.git && cd castrum
    make setup && . .venv/bin/activate
    
  2. Create inventory/<tenant>/hosts.yml with one host in managed, the group every playbook targets, and give the host its address in inventory/<tenant>/host_vars/web1.yml:

    managed:
      hosts:
        web1:
    # webservers:        # a host joins as many groups as apply
    #   hosts:
    #     web1:
    
    ansible_host: 192.0.2.10
    
  3. Install your key once, with password authentication this one time (needs sshpass on your machine and a host that accepts a root password login once):

    ansible-playbook ssh-copy-id.yml -i inventory/<tenant>/ -k \
      -e ssh_user=root -e "ssh_copy_id='$(cat ~/.ssh/id_ed25519.pub)'"
    

    castrum connects as root (ansible_user in group_vars/all.yml); a tenant that connects as someone else sets ansible_user in its hosts' host_vars (Per-host settings).

    A Windows PC also joins windows — managed alone sends it the Linux play — and runs scripts/bootstrap-windows-ssh.ps1 once, at the PC, instead of this step; set ansible_user in its host_vars to the account that script creates (Windows PCs).

  4. Dry run:

    ansible-playbook fortify.yml -i inventory/<tenant>/ --check --diff
    
  5. Read the diff, then run the same command without --check.

Pass the tenant directory, never inventory/ itself, which would merge every tenant into one run (Tenants and inventories).

Common usage

  • fortify.yml applies the baseline: hardening, firewall, the service roles a host's groups switch on, and sysadmin tooling.
  • os-update.yml updates packages, or runs Windows Update; -e os_update_auto=reboot or shutdown acts when an update wants a reboot.
  • ssh-copy-id.yml installs a public key for a user. Linux only.
ansible-playbook fortify.yml -i inventory/<tenant>/                   # the whole tenant
ansible-playbook fortify.yml -i inventory/<tenant>/ -e target=web1    # one host
ansible-playbook fortify.yml -i inventory/<tenant>/ --tags firewall   # one concern
ansible-playbook fortify.yml -i inventory/<tenant>/ --tags zram       # one role
ansible-playbook os-update.yml -i inventory/<tenant>/ -e os_update_auto=reboot

Two modes are passed with -e, for one run only (Modes):

  • update_only=true skips the install-and-configure work of the roles that implement it.
  • uninstall=true deletes the castrum firewall table until the next ordinary run.

Documentation

License

GNU General Public License, version 3 or later (GPL-3.0-or-later); see COPYING.

Agent policy seeded from Agent Foundry by Claudio Maradonna.