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
- Make a directory for templates:
mkdir -p tpl gen
- 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}
- Validate YAML syntax:
cloud-init schema --config-file tpl/userData.yml
Updating main.tf
- 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"
}
- 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
}
- Apply configuration:
terraform init
terraform apply
Verify Configuration
- Check web page:
curl http://<your-server-ip>
Success
You should see:
I'm Nginx @ "<your-server-ip>" created ...
- 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).
- Check fail2ban:
sudo fail2ban-client status sshd
Info
fail2ban protects your server from SSH login attemps
- 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.