Infrastructure as Code for our server(s)
Find a file
2025-12-20 23:39:31 +01:00
ansible fix(nextcloud): handle OIDC flows with nginx 2025-12-20 23:39:31 +01:00
.gitignore feat: adds user luke + dirkx 2025-10-25 20:12:22 +02:00
.terraform.lock.hcl feat: initial commit 2025-10-24 03:24:21 +02:00
ansible-inventory.tf feat: initial commit 2025-10-24 03:24:21 +02:00
ansible-inventory.yml.tmpl feat: initial commit 2025-10-24 03:24:21 +02:00
cloud-init-hetzner.yml feat: initial commit 2025-10-24 03:24:21 +02:00
main.tf feat: increase volume size 2025-10-28 16:17:55 +01:00
output.tf feat: initial commit 2025-10-24 03:24:21 +02:00
README.md docs: clariy requirement for vault env var 2025-12-20 22:27:23 +01:00

Hetzner Cloud Infrastructure

A comprehensive infrastructure-as-code project that provisions and configures a secure Ubuntu server on Hetzner Cloud using Terraform and Ansible. This setup provides a hardened, production-ready server with container support and security best practices.

🏗️ Architecture Overview

This project uses a two-stage approach:

  1. Terraform/OpenTofu: Provisions the cloud infrastructure

    • Creates a Hetzner Cloud server with data volume
    • Configures SSH keys and initial cloud-init setup
    • Generates Ansible inventory automatically
  2. Ansible: Configures and hardens the server

    • Updates system packages
    • Hardens SSH configuration
    • Sets up firewall rules (UFW)
    • Mounts data volume
    • Installs Docker (container runtime)
    • Applies security hardening measures

🔧 Features

  • Security Hardening: SSH hardening, firewall configuration, fail2ban setup
  • Container Ready: Docker installation for containerized applications
  • Data Persistence: Separate data volume for application data
  • Automated Setup: Complete infrastructure provisioning and configuration
  • Best Practices: Follows security and operational best practices

📋 Prerequisites

Before you begin, ensure you have:

  • SSH Key Pair: Public and private keys (default expects ~/.ssh/HetznerPersonal.pub)
  • Hetzner Cloud API Token: Valid API token with appropriate permissions
  • Terraform/OpenTofu: Version-compatible with the configuration
  • Ansible: Latest version with required collections
  • Required Ansible Collections:
    ansible-galaxy collection install community.general ansible.posix community.docker
    

⚠️ Important: Deployment should only be attempted from Luke's machine for the moment.

🚀 Quick Start

1. Configure Terraform Variables

Create a secret.tfvars file with your Hetzner Cloud API token:

hcloud_token = "your-hetzner-cloud-api-token-here"

2. Provision Infrastructure

# Initialize Terraform/OpenTofu
tofu init

# Review the planned changes
tofu plan -var-file=./secret.tfvars

# Apply the configuration
tofu apply -var-file=./secret.tfvars

This will:

  • Create a Hetzner Cloud server with data volume
  • Generate an Ansible inventory file
  • Output the server's IP address

3. Configure the Server

cd ansible

# Set a password for the administrative user
# This password will be used by Ansible for privilege escalation
ssh root@$(tofu -chdir=../ output -raw server_ip) passwd user

# Export the vault password
export VAULT_PW_ARGUMENT="--vault-password-file .vault_password.txt"

# Run the Ansible playbook to configure and harden the server
./run.sh

The Ansible playbook will:

  • Update all system packages
  • Harden SSH configuration (disable root login, change port)
  • Configure UFW firewall (allow SSH, HTTP, HTTPS)
  • Mount the data volume
  • Install Docker container runtime
  • Apply additional security hardening
  • Reboot the server

📁 Project Structure

./
├── main.tf                    # Terraform main configuration
├── output.tf                  # Terraform outputs
├── ansible-inventory.tf       # Generates Ansible inventory
├── ansible-inventory.yml.tmpl # Ansible inventory template
├── cloud-init-hetzner.yml     # Initial server configuration
├── secret.tfvars              # Your API token (create this)
└── ansible/
    ├── main.yml               # Main Ansible playbook
    ├── inventory.yml          # Generated inventory file
    ├── run.sh                 # Ansible execution script
    ├── vars/
    │   └── variables.yml      # Ansible variables
    └── roles/
        ├── apt-update/        # Package updates
        ├── basic-firewall/    # UFW firewall setup
        ├── data-volume/       # Data volume mounting
        ├── docker/           # Docker installation
        ├── ssh-daemon/       # SSH hardening
        └── restart-server/   # Server reboot

🔒 Security Features

  • SSH Hardening:

    • Root login disabled
    • Password authentication disabled
    • SSH port changed from default
    • Strong cryptographic algorithms enforced
    • Session timeouts configured
  • Firewall Configuration:

    • UFW enabled with deny-by-default policy
    • SSH, HTTP, HTTPS ports allowed
    • SSH brute force protection
  • System Hardening:

    • Fail2ban configured for SSH protection
    • Package updates applied
    • Secure user account created with sudo privileges

🐳 Container Support

The server includes Docker for containerized applications:

  • Docker installed from official Docker repository
  • Includes Docker Compose for multi-container applications
  • Data volume mounted for persistent storage

🔧 Customization

Modifying Server Configuration

Edit main.tf to change:

  • Server instance type and location
  • Volume size and configuration
  • SSH key path

Modifying Ansible Configuration

Edit ansible/main.yml to:

  • Enable/disable roles
  • Add new configuration roles
  • Modify execution order

Adding Secrets

Create ansible/secrets.yml.enc for encrypted variables:

ansible-vault create ansible/secrets.yml.enc

🚨 Important Notes

  • SSH Port: The server uses a non-default SSH port (configured in cloud-init)
  • Root Access: Root SSH access is permanently disabled after Ansible runs
  • Data Volume: The data volume is mounted for persistent storage
  • Firewall: UFW is configured to block all traffic except SSH, HTTP, and HTTPS

🔍 Troubleshooting

Common Issues

  1. Permission Denied: Verify your SSH key is correctly configured
  2. Ansible Connection Failed: Check that the server is fully provisioned and accessible

Useful Commands

# Check server status
tofu output server_ip

# Connect to server (use the correct SSH port from your configuration)
ssh -p <ssh_port> user@$(tofu output -raw server_ip)

# Check Ansible connectivity
cd ansible && ansible all -m ping -i inventory.yml

# Re-run specific Ansible roles
cd ansible && ansible-playbook main.yml --tags "firewall" -i inventory.yml

🧹 Cleanup

To destroy the infrastructure:

tofu destroy -var-file=./secret.tfvars

Warning: This will permanently delete your server and all data. Ensure you have backups of any important data stored on the server.

📝 License

This project is part of the Makerspace Leiden infrastructure.