From 79b1eed55aee339778c355cbdab3da061749ff3e Mon Sep 17 00:00:00 2001 From: Pim Kunis Date: Mon, 8 May 2023 16:06:48 +0200 Subject: [PATCH] merge with tf-debian-vm --- debian/files/cloud_init.cfg.tftpl | 53 +++++++++ debian/files/get_cert.sh | 17 +++ debian/files/network_config.cfg.tftpl | 16 +++ debian/main.tf | 157 ++++++++++++++++++++++---- 4 files changed, 221 insertions(+), 22 deletions(-) create mode 100644 debian/files/cloud_init.cfg.tftpl create mode 100755 debian/files/get_cert.sh create mode 100644 debian/files/network_config.cfg.tftpl diff --git a/debian/files/cloud_init.cfg.tftpl b/debian/files/cloud_init.cfg.tftpl new file mode 100644 index 0000000..1c00537 --- /dev/null +++ b/debian/files/cloud_init.cfg.tftpl @@ -0,0 +1,53 @@ +#cloud-config +hostname: "${name}" +manage_etc_hosts: true +disable_root: false +timezone: Europe/Amsterdam + +ssh_authorized_keys: +%{ for key in admin_authorized_keys ~} + - "${key}" +%{ endfor ~} + +%{ if insecure_password } +chpasswd: + list: | + root:root + expire: False +ssh_pwauth: true +%{ else } +ssh_pwauth: false +%{ endif } + +%{ if use_host_cert } +ssh_keys: + ed25519_private: | + ${indent(4, private_key)} + ed25519_certificate: "${host_cert}" +%{ endif} + +write_files: +- path: /etc/default/locale + content: | + LC_ALL=en_US.UTF-8 + LANG=en_US.UTF-8 +- path: /etc/locale.gen + content: | + en_US.UTF-8 UTF-8 + +runcmd: +- dhclient -r +- dhclient +- locale-gen + +%{ if data_share != "" } +mounts: +- ["data", "${data_share}", "9p", "trans=virtio,rw", "0", "0"] +%{ endif } + +%{ if fixed_dns != "" } +manage_resolv_conf: true +resolv_conf: + nameservers: + - "${fixed_dns}" +%{ endif } diff --git a/debian/files/get_cert.sh b/debian/files/get_cert.sh new file mode 100755 index 0000000..b6a6f85 --- /dev/null +++ b/debian/files/get_cert.sh @@ -0,0 +1,17 @@ +#!/bin/bash +set -euo pipefail +IFS=$'\n\t' + +eval "$(jq -r '@sh "PUBKEY=\(.pubkey) HOST=\(.host) CAHOST=\(.cahost) CASCRIPT=\(.cascript) CAKEY=\(.cakey)"')" + +# TODO: Can this be done more eye-pleasingly? +set +e +CERT=$(ssh -o ConnectTimeout=3 -o ConnectionAttempts=1 root@$CAHOST '"'"$CASCRIPT"'" host "'"$CAKEY"'" "'"$PUBKEY"'" "'"$HOST"'".dmz') +retval=$? +set -e + +if [ retval -neq 0 ]; then + CERT="" +fi + +jq -n --arg cert "$CERT" '{"cert":$cert}' diff --git a/debian/files/network_config.cfg.tftpl b/debian/files/network_config.cfg.tftpl new file mode 100644 index 0000000..45499a5 --- /dev/null +++ b/debian/files/network_config.cfg.tftpl @@ -0,0 +1,16 @@ +version: 2 +ethernets: + ens: + match: + name: ens* +%{ if fixed_address != "" } + dhcp4: false + addresses: + - "${fixed_address}" +%{ else } + dhcp4: true +%{ endif } + dhcp4: true + routes: + - to: 0.0.0.0/0 + via: 192.168.30.1 diff --git a/debian/main.tf b/debian/main.tf index e008fd9..bc0db5c 100644 --- a/debian/main.tf +++ b/debian/main.tf @@ -10,27 +10,140 @@ module "invariants" { source = "git::https://git.pim.kunis.nl/home/tf-modules.git//invariants" } -module "tf_debian_vm" { - source = "git::https://git.pim.kunis.nl/pim/tf-debian-vm.git" - name = var.name - domain_name = var.domain_name +locals { admin_authorized_keys = coalesce(var.admin_authorized_keys, module.invariants.admin_authorized_keys) - insecure_password = var.insecure_password - use_host_cert = var.use_host_cert - disk_pool = coalesce(var.disk_pool, module.invariants.disk_pool) - disk_base = coalesce(var.disk_base, module.invariants.disk_base) - disk_base_pool = coalesce(var.disk_base_pool, module.invariants.disk_base_pool) - cloudinit_pool = coalesce(var.cloudinit_pool, module.invariants.cloudinit_pool) - bridge_name = coalesce(var.bridge_name, module.invariants.bridge_name) - ca_host = module.invariants.ca_host - ca_script = module.invariants.ca_script - ca_key = var.ca_key - memory = var.memory - fixed_address = var.fixed_address - ansible_command = var.ansible_command - mac = var.mac - fixed_dns = var.fixed_dns - disk_size = var.disk_size - data_share = var.data_share - hypervisor_host = var.hypervisor_host + cloudinit_user_data = templatefile("${path.module}/files/cloud_init.cfg.tftpl", { + name = var.name, + admin_authorized_keys = local.admin_authorized_keys, + insecure_password = var.insecure_password, + use_host_cert = var.use_host_cert, + host_cert = trimspace(null_resource.cert.triggers["cert"]), + private_key = tls_private_key.debian.private_key_openssh, + fixed_dns = var.fixed_dns + data_share = var.data_share + }) + cloudinit_network_config = templatefile("${path.module}/files/network_config.cfg.tftpl", { + fixed_address = var.fixed_address + }) + domain_name = coalesce(var.domain_name, var.name) + disk_pool = coalesce(var.disk_pool, module.invariants.disk_pool) + disk_base = coalesce(var.disk_base, module.invariants.disk_base) + disk_base_pool = coalesce(var.disk_base_pool, module.invariants.disk_base_pool) + cloudinit_pool = coalesce(var.cloudinit_pool, module.invariants.cloudinit_pool) + bridge_name = coalesce(var.bridge_name, module.invariants.bridge_name) +} + +resource "tls_private_key" "debian" { + algorithm = "ED25519" +} + +data "tls_public_key" "debian" { + private_key_pem = tls_private_key.debian.private_key_pem +} + +data "external" "cert" { + program = ["bash", "${path.module}/files/get_cert.sh"] + + query = { + pubkey = trimspace(data.tls_public_key.debian.public_key_openssh) + host = var.name + cahost = module.invariants.ca_host + cascript = module.invariants.ca_script + cakey = var.ca_key + } +} + +resource "null_resource" "cert" { + triggers = { + cert = data.external.cert.result["cert"] + } + + lifecycle { + ignore_changes = [ + triggers + ] + + postcondition { + condition = data.external.cert.result["cert"] != "" || !var.use_host_cert + error_message = "Error retrieving host certificate." + } + } +} + +resource "libvirt_volume" "debian" { + name = "${local.domain_name}.iso" + pool = local.disk_pool + size = var.disk_size + base_volume_name = local.disk_base + base_volume_pool = local.disk_base_pool + + lifecycle { + replace_triggered_by = [ + libvirt_cloudinit_disk.debian.id + ] + } +} + +resource "libvirt_cloudinit_disk" "debian" { + name = "${local.domain_name}.iso" + pool = local.cloudinit_pool + user_data = local.cloudinit_user_data + network_config = local.cloudinit_network_config +} + +resource "null_resource" "data_share" { + connection { + type = "ssh" + user = "root" + host = var.hypervisor_host + } + + provisioner "remote-exec" { + inline = [ + "if [ \"${var.data_share}\" != \"\"; then mkdir -p --mode=og=rwx /data/${local.domain_name}; fi" + ] + } +} + +resource "libvirt_domain" "debian" { + name = local.domain_name + memory = var.memory + vcpu = 4 + autostart = true + + disk { + volume_id = libvirt_volume.debian.id + } + + dynamic "filesystem" { + for_each = var.data_share != "" ? [1] : [] + + content { + source = "/data/${local.domain_name}" + target = "data" + readonly = false + } + } + + network_interface { + bridge = local.bridge_name + hostname = var.name + mac = var.mac + } + + cloudinit = libvirt_cloudinit_disk.debian.id + + provisioner "local-exec" { + command = var.ansible_command + } + + lifecycle { + replace_triggered_by = [ + libvirt_cloudinit_disk.debian.id + ] + } + + depends_on = [ + null_resource.data_share + ] }