Working On Cloud Init

Technical Background

Cloud-init is a powerful tool widely used to customize cloud instances during the first boot. It supports executing shell commands, configuring system users, installing packages, and modifying services without manual SSH access. This automation ensures consistent and secure configuration across deployments.

In this exercise, Terraform’s user_data parameter is used to provide a cloud-config YAML file. This configuration automatically installs and configures Nginx, creates a secure devops user for SSH, disables password-based login and root access, and implements additional hardening measures like fail2ban. These steps ensure both a functioning web server and basic security measures against brute force attacks.

Warning

Cloud-init uses YAML syntax, which is indentation-sensitive. A single space or missing indentation can cause a boot failure or prevent services from starting. Always validate your cloud-init files using:

cloud-init schema --config-file userData.yml

Solution

Prerequisits

Create a main.tf, outputs.tf, variables.tf, network.tf,providers.tf and secrets.auto.tfvars like in 12 Automatic Nginx Installation.

Creating Cloud-Init Configuration

  1. Make a directory for templates:
mkdir -p tpl gen
  1. Create tpl/userData.yml:
#cloud-config

# upgrade distribution at server creation time
package_update: true
package_upgrade: true
package_reboot_if_required: true

# disable root login
disable_root: true

# disable ssh password login
ssh_pwauth: false

users:
  - name: jr125
    groups: sudo
    shell: /bin/bash
    sudo: ALL=(ALL) NOPASSWD:ALL
    ssh_authorized_keys:
      - ${devopsSSHPublicKey}
      - ${host_ed25519_public}

packages:

  # webserver
  - nginx

  # ssh security
  - fail2ban

  # file indexer
  - plocate

runcmd:
  - systemctl enable fail2ban
  - systemctl start fail2ban
  - >
    echo "[sshd]
    enabled = true
    port = ssh
    logpath = %(sshd_log)s
    maxretry = 3
    bantime = 3600" > /etc/fail2ban/jail.d/ssh.conf
  - systemctl restart fail2ban

  - systemctl enable nginx
  - rm /var/www/html/*
  - >
    echo "I'm Nginx @ $(dig -4 TXT +short o-o.myaddr.l.google.com @ns1.google.com) 
    created $(date -u)" >> /var/www/html/index.html

  - systemctl enable plocate-updatedb.timer
  - systemctl start plocate-updatedb.timer
  - updatedb

ssh_keys:
  ed25519_private: |
    ${host_ed25519_private}
  1. Validate YAML syntax:
cloud-init schema --config-file tpl/userData.yml

Updating main.tf

  1. Create dynamic ressource for tpl/userData.yml:
resource "local_file" "user_data" {
  content = templatefile("tpl/userData.yml", {
    host_ed25519_private = indent(4, tls_private_key.host.private_key_openssh)
    devopsSSHPublicKey   = hcloud_ssh_key.loginUser.public_key
  })
  filename = "gen/userData.yml"
}
  1. Update server ressource to use tpl/userData.yml:
resource "hcloud_server" "web" {
  name         = "web"
  image        = "debian-12"
  server_type  = "cx22"
  firewall_ids = [hcloud_firewall.sshFw.id]
  ssh_keys     = [hcloud_ssh_key.loginUser.id]
  user_data    = local_file.user_data.content
}
  1. Apply configuration:
terraform init
terraform apply

Verify Configuration

  1. Check web page:
curl http://<your-server-ip>

Success

You should see:

I'm Nginx @ "<your-server-ip>" created ...

  1. Check secure SSH:
ssh devops@<your-server-ip>
sudo su -
hostname

Fail

Root login via SSH should fail:

bash ssh root@<your-server-ip> Permission denied (publickey).

  1. Check fail2ban:
sudo fail2ban-client status sshd

Info

fail2ban protects your server from SSH login attemps

  1. Check file index:
locate ssh_host

Warning

Not updating packages can lead to security vulnerabilities.

Success

After these steps, you have a fully automated and secure server deployment including package updates, Nginx, fail2ban, and plocate.

Terraform Hetzner Docs

Cloud Stack Talk

cloud-init docs

Validation

Debugging

systemctl

Terraform TLS

Terraform Local docs

Terraform YAML

SSHD Config

Cloud Config

Sudo Access

Debian 12 Fix