• Cloud provider
    • Hetzner cloud administration GUI
sdiuser@martin-pc-dachboden:~$ ssh-keygen -t ed25519 
Generating public/private ed25519 key pair.
Enter file in which to save the key (/home/sdiuser/.ssh/id_ed25519): 
Created directory '/home/sdiuser/.ssh'.
Enter passphrase (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved in /home/sdiuser/.ssh/id_ed25519 
Your public key has been saved in /home/sdiuser/.ssh/id_ed25519.pub 

Upon confirmation your Hetzner project space sdi_gxy (e.g. sdi_g01 corresponding to group 1) should be accessible.

  1. Create a default firewall allowing ping and ssh

  2. Ubuntu latest

  3. Shared vCPU / x86 / CX11 (the cheapest)

  4. Add your personal ssh public key from Figure 965, “Preliminary: Create an ssh key pair”

  1. Omit volume, labels and cloud configuration

  2. Note the Networking / Public IPv4 address for later reference

  3. Click »Create & Buy now«

  • Ping your server:

    Note

    The IP 91.107.232.156 serves just as a sample value irrespective of your individual actual server IP.

    sdiuser:~$ ping 91.107.232.156
    PING 91.107.232.156 (91.107.232.156) 56(84) bytes of data.
    64 bytes from 91.107.232.156: icmp_seq=1 ttl=49 time=18.3 ms
    64 bytes from 91.107.232.156 ...
  • Login via ssh:

    ssh root@91.107.232.156
  1. apt update

  2. apt upgrade

  3. reboot

root@topsy:~# apt install nginx
root@topsy:~# wget -O - 91.107.232.156
--2024-04-07 18:59:13--  http://91.107.232.156/
Connecting to 91.107.232.156:80... connected.
<html>
<head>
<title>Welcome to nginx!</title> ...

Point your browser to http://91.107.232.156.

sdiuser:~$ telnet 91.107.232.156 80
Trying 91.107.232.156...

Why is there no answer?

sdiuser:~$ telnet 91.107.232.156 80
Trying 91.107.232.156...
Connected to 91.107.232.156.
Escape character is '^]'

Congrats: External Browser access is working now!

Caution

This is about $$$ MONEY $$$

  • Delete your server including the IPv4 address.

  • You may delete your firewall

Quote:

Terraform is an infrastructure as code tool that lets you build, change, and version cloud and on-prem resources safely and efficiently.

  • Access you cloud project using the Hetzner GUI interface.

  • Go to Security --> API Tokens --> Generate API token

  • Provide a name and hit Generate API token.

  • Copy the generated token's value and store it in a secure location e.g. a password manager.

    Caution

    The Hetzner GUI blocks future access to the token.

# Define Hetzner cloud provider
terraform {
  required_providers {
    hcloud = {
      source = "hetznercloud/hcloud"
    }
  }
  required_version = ">= 0.13"
}

# Configure the Hetzner Cloud API token
provider "hcloud" {
  token = "your_api_token_goes_here"
}

# Create a server
resource "hcloud_server" "helloServer" {
  name         = "hello"
  image        =  "debian-12"
  server_type  =  "cx11"   
}
$ terraform init

Initializing the backend...

Initializing provider plugins...
- Finding latest version of hetznercloud/hcloud...
- Installing hetznercloud/hcloud v1.46.1...
- Installed hetznercloud/hcloud v1.46.1 (signed by a HashiCorp partner, key ID 5219EACB3A77198B)

...
Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above.  ...
$ terraform plan

Terraform used the selected providers to generate the following execution plan. Resource actions ...
  + create

Terraform will perform the following actions:

  # hcloud_server.helloServer will be created
  + resource "hcloud_server" "helloServer" {
      + allow_deprecated_images    = false
      + backup_window              = (known after apply)
  ...
    }

Plan: 1 to add, 0 to change, 0 to destroy.
$ terraform apply
...
Plan: 1 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

hcloud_server.helloServer: Creating...
hcloud_server.helloServer: Still creating... [10s elapsed]
hcloud_server.helloServer: Creation complete after 14s [id=45822789]

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
Your server "hello" was created!

You can access your server with the following credentials:
 
IPv4	128.140.108.60
IPv6	2a01:4f8:1c1c:8e3a::/64
User	root
Password	rJ3pNvJXbqMp3XNTvFdq

You will be prompted to change your password on your first login.

To improve security, we recommend that you add an SSH key when creating a server.
  • Firewall blocks ssh server access:

    $ ssh root@128.140.108.60
    ssh: connect to host 128.140.108.60 port 22: Connection refused

    Access by Vnc console login only

  • IP and (initial) credentials by email 😱

Solution:

  1. Add firewall inbound ssh access rule.

  2. Configure ssh public key login.

resource "hcloud_firewall" "sshFw" {
  name = "ssh-firewall"
  rule {
    direction = "in"
    protocol  = "tcp"
    port      = "22"
    source_ips = ["0.0.0.0/0", "::/0"]
  }
}
              ...
resource "hcloud_server" "helloServer" {
              ...
  firewall_ids = [hcloud_firewall.sshFw.id]
}
resource "hcloud_ssh_key" "loginUser" {
  name       = "goik@hdm-stuttgart.de"
  public_key = file("~/.ssh/id_ed25519.pub")
}
              ...
resource "hcloud_server" "helloServer" {
              ...
  ssh_keys     = [hcloud_ssh_key.loginUser.id]
}
$ terraform apply

  # hcloud_firewall.sshFw will be created
  + resource "hcloud_firewall" "sshFw" {
       ...
  # hcloud_server.helloServer will be created
  + resource "hcloud_server" "helloServer" {
       ...
  # hcloud_ssh_key.goik will be created
  + resource "hcloud_ssh_key" "loginUser" {
       ...
Plan: 3 to add, 0 to change, 0 to destroy.
       ...
Apply complete! Resources: 3 added, 0 changed, 0 destroyed.

See terraform output documentation:

File outputs.tf Result
output "hello_ip_addr" {
  value       = hcloud_server.helloServer.ipv4_address
  description = "The server's IPv4 address"
}

output "hello_datacenter" {
  value       = hcloud_server.helloServer.datacenter
  description = "The server's datacenter"
}
$ terraform output
hello_datacenter = "nbg1-dc3"
hello_ip_addr = "159.69.152.37"
$ terraform output hello_ip_addr
"159.69.152.37"
File outputs.tf terraform output -json
output "hello_ip_addr" {
  value       = hcloud_server.helloServer.ipv4_address
  description = "The server's IPv4 address"
}

output "hello_datacenter" {
  value       = hcloud_server.helloServer.datacenter
  description = "The server's datacenter"
}
{
  "hello_datacenter": {
    "sensitive": false,
    "type": "string",
    "value": "nbg1-dc3"
  },
  "hello_ip_addr": {
    "sensitive": false,
    "type": "string",
    "value": "159.69.152.37"
  }
}

Versioned file main.tf:

...
provider "hcloud" { token = "xdaGfz9LmwO8SWkg ... "}
...

Solution:

  • Declare a variable hcloud_token in a variables.tf file

  • Add a non-versioned file secrets.auto.tfvars.

  • Optional: Provide a versioned secrets.auto.tfvars.template documenting file

Declaring hcloud_token in variables.tf:

variable "hcloud_token" {  # See secret.auto.tfvars
  nullable = false
  sensitive = true
}

Defining hcloud_token's value in secrets.auto.tfvars:

hcloud_token="xdaGfz9LmwO8SWkg ... "

Using hcloud_token in main.tf:

provider "hcloud" { token = var.hcloud_token }

Example in secrets.auto.tfvars.template:

hcloud_token="your_api_token_goes_here"
  1. Incrementally creating a base system
  • Cloud provider
    • Cloud-init
  • Distribution image containing pre-installed Cloud Init

  • Script configurable installation options

  • Individual CRUD file operations

  • Supplying ssh user and host keys.

  • Adding users

  • ...

  • Installing packages

  • System Upgrade + reboot

  • Arbitrary command execution

resource "hcloud_server" "web" {
  name         = var.server_name
       ...
  user_data = file("userData.yml")
}
#cloud-config
packages:
  - nginx
runcmd:
  - 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

Problem of repeated terraform apply:

$ ssh root@128.140.108.60
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@    WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!     @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
Someone could be eavesdropping on you right now (man-in-the-middle attack)!
root@hello:~# journalctl -f
May 06 04:41:20 hello cloud-init[898]: Cloud-init v. 22.4.2 finished at Mon, 06 May 2024 04:41:20 +0000. Datasource DataSourceHetzner.  Up 11.78 seconds
   ...
May 06 04:46:16 hello sshd[927]: Invalid user abc from 43.163.218.130 port 33408
May 06 04:46:17 hello sshd[927]: Received disconnect from 43.163.218.130 port 33408:11: Bye Bye [preauth]
May 06 04:46:17 hello sshd[927]: Disconnected from invalid user abc 43.163.218.130 port 33408 [preauth]
   ...
May 06 04:50:54 hello sshd[930]: fatal: Timeout before authentication for 27.128.243.225 port 59866
   ...
May 06 04:52:45 hello sshd[933]: Invalid user cos from 43.163.218.130 port 59776
   ...
May 06 04:53:04 hello sshd[935]: Invalid user admin from 194.169.175.35 port 51128
May 06 04:53:49 hello sshd[937]: User root from 43.163.218.130 not allowed because not listed in AllowUsers
May 06 04:53:49 hello sshd[937]: Disconnected from invalid user root 43.163.218.130 port 50592 [preauth]
  1. Working on Cloud-init
  • Cloud provider
    • ➟ Volumes
resource "hcloud_server" "helloServer" {
  server_type  =  "cx11"
...
}

resource "hcloud_volume" "volume01" {
  name      = "volume1"
  size      = 10
  server_id = hcloud_server.helloServer.id
  automount = true
  format    = "xfs"
}
df
...
/mnt/HC_Volume_100723816
output "volume_id" {
 value=hcloud_volume.volume01.id
 description = "The volume's id"
}
# ls /dev/disk/by-id/*100723816
/dev/disk/by-id/scsi-0HC_Volume_100723816
terraform apply
...
hello_ip_addr="37.27.22.189"
volume_id="100723816"

Desired /etc/fstab:

/dev/disk/by-id/scsi-0HC_Volume_100723816 
  /volume01 xfs discard,nofail,defaults 0 0
  1. Auto mounting a volume
resource "hcloud_server" "helloServer" {
    ...
  user_data = templatefile("tpl/userData.yml", {
     ...
    volume01Id = hcloud_volume.volume01.id
  })
}
resource "hcloud_volume" "volume01" {
  server_id = hcloud_server.helloServer.id
  ...
}

Problem: Cyclic dependency

helloServer <--> volume01
resource "hcloud_volume" "volume01" {
  size = 10
    ....
}
resource "hcloud_server" "helloServer" {
  user_data    = templatefile("tpl/userData.yml", {
    volume01Id = hcloud_volume.volume01.id # No cycle any more 
      ...
  })  ...
}
resource "hcloud_volume_attachment" "main" {
  volume_id = hcloud_volume.volume01.id
  server_id = hcloud_server.helloServer.id
}
  1. Mount point's name specification