# Hardened Fedora Minecraft Server (Ansible) Ansible playbook that turns a fresh **Fedora 42** server into a hardened, containerized **Minecraft** game server β€” Java *and* Bedrock clients β€” running with **rootless Podman** via systemd Quadlet units. Everything is managed declaratively from `harden_fedora_minecraft.yml`: OS hardening, firewall, SELinux, SSH access control, the Podman runtime, and the Minecraft/Geyser containers are all deployed by one command. ## Features - πŸ”’ **[Hardening]** SELinux enforcing, firewalld default-DROP policy, password policy (pwquality + aging), root account locked, automatic security updates (`dnf-automatic`). - πŸ”‘ **[SSH]** Key-only authentication for a dedicated admin user, root login disabled, `AllowUsers` restriction, and a `fail2ban` SSH jail. - 🐳 **[Runtime]** Rootless Podman with user lingering, subuid/subgid ranges, IPv4 forwarding, and the overlay storage driver. - ⛏️ **[Minecraft]** Portable Java server built from [`itzg/docker-minecraft-server`](https://github.com/itzg/docker-minecraft-server) (PAPER by default, version/memory/difficulty configurable). - πŸͺ¨ **[Geyser]** A second container, [`geysermc/geyser`](https://github.com/GeyserMC/geyser-docker), in the same pod so **Bedrock** (phone/console) players can join the Java world. - πŸ—„οΈ **[Data]** World data and backups live on the host in `/srv/minecraft` with correct SELinux labels. ## Architecture Both game containers run in a single Podman **pod** (shared network namespace) as the non-root user `mcadmin`: ``` Internet β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ firewall (DROP)β”‚ only the ports below are allowed β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ pod: minecraft (rootless, user mcadmin) β”œβ”€β”€ minecraft-server JVM on 0.0.0.0:25565 (Java clients) β”‚ └── world data β†’ /srv/minecraft/data └── geyser Bedrock listener :19132/udp └── config β†’ /srv/minecraft/geyser └── forwards Bedrock players to 127.0.0.1:25565 ``` | Port | Protocol | Purpose | |------|----------|---------| | `22` | TCP | SSH (configurable via `ssh_port`) | | `25565` | TCP | Minecraft Java server | | `25565` | UDP | Optional Java server UDP (game query) | | `19132` | UDP | Bedrock (Geyser), when `enable_geyser: true` | ## Repository layout ``` . β”œβ”€β”€ harden_fedora_minecraft.yml # entry point: hosts: external-apps β†’ 7 roles β”œβ”€β”€ inventory.yml # target host definition β”œβ”€β”€ ansible.cfg # inventory + role path + pipelining β”œβ”€β”€ requirements.yml # Ansible collections (ansible.posix) β”œβ”€β”€ group_vars/all.yml # shared variables β”œβ”€β”€ host_vars/external-apps.yml # per-server settings (IP, SSH key, MC tuning) └── roles/ β”œβ”€β”€ packages/ # base + container tooling, full dnf update, dnf-automatic β”œβ”€β”€ firewall/ # firewalld: DROP target, open SSH + Minecraft + Geyser β”œβ”€β”€ selinux/ # enforcing mode + container_file_t context for /srv/minecraft β”œβ”€β”€ auth/ # admin user, SSH key, sudoers, password policy, lock root β”œβ”€β”€ ssh_harden/ # sshd drop-in + fail2ban jail β”œβ”€β”€ podman/ # socket, linger, subuid/subgid, ip_forward, storage └── minecraft/ # data dirs, Quadlet pod/container units, image pulls ``` ## Prerequisites - Ansible control node (this repo) with Ansible Core β‰₯ 2.14 and `ansible.posix` - A Fedora 42 target server reachable over SSH with an account that has sudo - The `ansible.user` in the inventory must be able to `become` root on the target ## Quick start ```bash # 1. Install the required collection ansible-galaxy collection install -r requirements.yml # 2. Configure the target server (REQUIRED before first run) # - host_vars/external-apps.yml # * ansible_host: real IP or DNS name # * admin_user_ssh_key: your real SSH public key # * game settings to taste (version, memory, whitelist…) # 3. Run the playbook ansible-playbook harden_fedora_minecraft.yml ``` > ⚠️ `admin_user_ssh_key` **must** be a real key before the first run β€” > the playbook disables SSH password authentication and root login once > the `ssh_harden` role executes. ## Configuration All tunables live in `host_vars/external-apps.yml` (per server) and `group_vars/all.yml` (cluster-wide). Highlights: | Variable | Default | Meaning | |----------|---------|---------| | `admin_user` | `mcadmin` | Non-root user running the containers | | `minecraft_version` | `LATEST` | Server version (`1.21.1`, …) | | `minecraft_server_type` | `PAPER` | PAPER / VANILLA / FABRIC / FORGE | | `minecraft_memory` | `2G` | JVM memory limit | | `minecraft_whitelist` | `""` | Comma-separated allowed usernames (empty = off) | | `enable_geyser` | `true` | Deploy the Bedrock bridge | | `geyser_port` | `19132` | Bedrock UDP port | | `geyser_auth_type` | `online` | `offline` only with a paired Floodgate key | | `ssh_port` | `22` | SSH port used by firewall + sshd | ## Operations After the playbook has run, manage the servers as `mcadmin`: ```bash sudo machinectl shell mcadmin@ -- systemctl --user status minecraft-server geyser sudo machinectl shell mcadmin@ -- systemctl --user restart minecraft-server sudo machinectl shell mcadmin@ -- podman ps # container list sudo machinectl shell mcadmin@ -- journalctl --user-unit minecraft-server -f ``` ## References - [Ansible Documentation](https://docs.ansible.com/) - [Fedora Project](https://fedoraproject.org/) - [Podman](https://podman.io/) - [Podman Quadlet (systemd.unit) man page](https://docs.podman.io/en/latest/markdown/podman-systemd.unit.5.html) - [Rootless Podman tutorial](https://github.com/containers/podman/blob/main/docs/tutorials/rootless_tutorial.md) - [itzg/docker-minecraft-server](https://github.com/itzg/docker-minecraft-server) - [GeyserMC β€” Bedrock ↔ Java bridge](https://geysermc.dev/) - [GeyserMC/Geyser](https://github.com/GeyserMC/Geyser) - [Geyser docker image](https://github.com/GeyserMC/geyser-docker) - [Firewalld](https://firewalld.org/) - [fail2ban](https://github.com/fail2ban/fail2ban) - [SELinux quick docs (Fedora)](https://docs.fedoraproject.org/en-US/quick-docs/selinux-getting-started/) ## License [MIT](./LICENSE)