This commit is contained in:
Florian Maury 2024-06-04 11:25:59 +02:00
commit 0dd3b5bdfe
50 changed files with 2019 additions and 0 deletions

8
.gitignore vendored Normal file
View file

@ -0,0 +1,8 @@
.terraform/
.terraform.lock.hcl
terraform.tfstate
**/*.iso
**/*.sig
**/*.ign
pve_api_token
settings.tfvars

12
LICENSE.md Normal file
View file

@ -0,0 +1,12 @@
# SPDX short identifier: BSD-3-Clause
Copyright 2024 Florian Maury
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

7
README.md Normal file
View file

@ -0,0 +1,7 @@
# Preuve de concept pour une infra virtualisée sur Proxmox avec Fedora CoreOS
## Contributions
Les contributions, commentaires, et pull-requests sont à envoyer à l'adresse florian.maury@metempsychose.fr.
Vous pouvez consulter [ce site](https://git-send-email.io/) pour en apprendre plus sur l'envoi de contributions par email.

59
main.tf Normal file
View file

@ -0,0 +1,59 @@
terraform {
required_providers {
proxmox = {
source = "bpg/proxmox"
version = "~>0.56.1"
}
}
required_version = ">=1.6.2"
}
provider "proxmox" {
endpoint = var.pve_api_base_url
api_token = var.pve_api_token
}
module "netboot_server" {
source = "./modules/netboot_server"
hostname = "netboot_server"
prod_network_name = var.admin_network_name
dhcp_iface = "ens18"
dhcp_server_ip_addr = cidrhost(var.admin_network_prefix, 2)
dhcp_gateway = cidrhost(var.admin_network_prefix, 1)
dhcp_range = var.admin_network_prefix
ssh_public_key_opentofu_netboot_server = var.ssh_public_key_opentofu_netboot_server
pve_api_base_url = var.pve_api_base_url
pve_api_token = var.pve_api_token
pve_node_name = var.pve_node_name
pve_storage_id = var.pve_storage_id
pve_vm_id = 108
}
module "poc" {
depends_on = [ module.netboot_server ]
source = "./modules/poc"
pve_vm_id = 110
pve_storage_id = "local"
pve_node_name = "ns3152888"
pve_ssh_user = var.pve_ssh_user
pve_ssh_host = var.pve_ssh_host
netboot_server_ip_address = cidrhost(var.admin_network_prefix, 2)
admin_network = {
name = var.admin_network_name
prefix = var.admin_network_prefix
mac_address = "1c:69:7a:ff:ff:01"
}
prod_network = {
name = var.prod_network_name
prefix = var.prod_network_prefix
mac_address = "1c:69:7a:ef:ff:01"
}
monitoring_network = {
name = var.monit_network_name
prefix = var.monit_network_prefix
mac_address = "1c:69:7a:df:ff:01"
}
admin_ssh_public_key = var.ssh_public_key_admin_netboot_server
}

View file

@ -0,0 +1,149 @@
locals {
caddy_data_filesystem = {
device = "${local.data_device_path}-part1"
format = "ext4"
label = "caddy_data"
}
caddy_data_volume_file = {
path = "/etc/containers/systemd/caddy_data.volume"
user = {id = 0}
group = {id = 0}
mode = 420 # 0644
contents = {
source = format(
"data:text/plain;base64,%s",
base64encode(file("${path.module}/files/caddy/caddy_data.volume"))
)
}
}
fcos_images_filesystem = {
device = "${local.data_device_path}-part4"
format = "ext4"
label = "fcos_images"
}
fcos_images_volume_file = {
path = "/etc/containers/systemd/fcos_images.volume"
user = {id = 0}
group = {id = 0}
mode = 420 # 0644
contents = {
source = format(
"data:text/plain;base64,%s",
base64encode(file("${path.module}/files/caddy/fcos_images.volume"))
)
}
}
image_downloader_image_file = {
path = "/etc/containers/systemd/image_downloader.image"
user = {id = 0}
group = {id = 0}
mode = 420 # 0644
contents = {
source = format(
"data:text/plain;base64,%s",
base64encode(file("${path.module}/files/caddy/image_downloader.image"))
)
}
}
image_downloader_container_file = {
path = "/etc/containers/systemd/image_downloader.container"
user = {id = 0}
group = {id = 0}
mode = 420 # 0644
contents = {
source = format(
"data:text/plain;base64,%s",
base64encode(file("${path.module}/files/caddy/image_downloader.container"))
)
}
}
caddy_builddir_dir = {
path = "/root/caddy"
user = {id = 0}
group = {id = 0}
mode = 448 # 0700
}
caddyfile_file = {
path = "/root/caddy/Caddyfile"
user = {id = 0}
group = {id = 0}
mode = 384 # 0600
contents = {
source = format(
"data:text/plain;base64,%s",
base64encode(file("${path.module}/files/caddy/Caddyfile"))
)
}
}
ipxe_script_file = {
path = "/root/caddy/ipxe.script"
user = {id = 0}
group = {id = 0}
mode = 384 # 0600
contents = {
source = format(
"data:text/plain;base64,%s",
base64encode(file("${path.module}/files/caddy/ipxe.script"))
)
}
}
caddy_containerfile_file = {
path = "/root/caddy/Containerfile"
user = {id = 0}
group = {id = 0}
mode = 384 # 0600
contents = {
source = format(
"data:text/plain;base64,%s",
base64encode(file("${path.module}/files/caddy/caddy.Containerfile"))
)
}
}
caddy_container_file = {
path = "/etc/containers/systemd/caddy.container"
user = {id = 0}
group = {id = 0}
mode = 420 # 0644
contents = {
source = format(
"data:text/plain;base64,%s",
base64encode(file("${path.module}/files/caddy/caddy.container"))
)
}
}
caddy_filesystems = [
local.caddy_data_filesystem,
local.fcos_images_filesystem,
]
caddy_directories = [
local.caddy_builddir_dir,
]
caddy_files = [
local.caddy_data_volume_file,
local.fcos_images_volume_file,
local.image_downloader_image_file,
local.image_downloader_container_file,
local.caddyfile_file,
local.ipxe_script_file,
local.caddy_containerfile_file,
local.caddy_container_file,
]
caddy_systemd_units = [
]
}

View file

@ -0,0 +1,126 @@
locals {
dhcp_config_path_systemd_unit = {
name = "dhcp_config.path"
enabled = true
contents = templatefile(
"${path.module}/files/dhcp/dhcp_config.path.tftpl",
{
path = "/var/lib/containers/storage/volumes/dhcp_config/_data/writable/"
}
)
}
dhcp_config_service_systemd_unit = {
name = "dhcp_config.service"
enabled = false
contents = file("${path.module}/files/dhcp/dhcp_config.service")
}
dhcp_data_filesystem = {
device = "${local.data_device_path}-part3"
format = "ext4"
label = "dhcp_data"
}
dhcp_data_volume_file = {
path = "/etc/containers/systemd/dhcp_data.volume"
user = {id = 0}
group = {id = 0}
mode = 420 # 0644
contents = {
source = format(
"data:text/plain;base64,%s",
base64encode(file("${path.module}/files/dhcp/dhcp_data.volume"))
)
}
}
dhcp_builddir_dir = {
path = "/root/dhcp"
user = {id = 0}
group = {id = 0}
mode = 448 # 0700
}
dnsmasq_base_config_file = {
path = "/root/dhcp/dnsmasq.conf"
user = {id = 0}
group = {id = 0}
mode = 420 # 0644
contents = {
source = format(
"data:text/plain;base64,%s",
base64encode(templatefile(
"${path.module}/files/dhcp/dnsmasq.conf.tftpl",
{
dhcp_server_ip_addr = var.dhcp_server_ip_addr
dhcp_range = split("/", var.dhcp_range)[0]
dhcp_range_netmask = cidrnetmask(var.dhcp_range)
dhcp_router = var.dhcp_gateway
config_extension_dir = "/etc/dnsmasq.d/writable/"
}
))
)
}
}
generate_dhcp_options_script_file = {
path = "/var/roothome/generate_dhcp_options.sh"
user = {id = 0}
group = {id = 0}
mode = 448 # 0700
contents = {
source = format(
"data:text/plain;base64,%s",
base64encode(file("${path.module}/files/dhcp/generate_dhcp_options.sh"))
)
}
}
dhcp_containerfile_file = {
path = "/root/dhcp/Containerfile"
user = {id = 0}
group = {id = 0}
mode = 420 # 0644
contents = {
source = format(
"data:text/plain;base64,%s",
base64encode(file("${path.module}/files/dhcp/dnsmasq.Containerfile"))
)
}
}
dhcp_container_file = {
path = "/etc/containers/systemd/dnsmasq_container.container"
user = {id = 0}
group = {id = 0}
mode = 420 # 0644
contents = {
source = format(
"data:text/plain;base64,%s",
base64encode(file("${path.module}/files/dhcp/dnsmasq_container.container"))
)
}
}
dhcp_filesystems = [
local.dhcp_data_filesystem,
]
dhcp_directories = [
local.dhcp_builddir_dir,
]
dhcp_files = [
local.dhcp_data_volume_file,
local.dnsmasq_base_config_file,
local.generate_dhcp_options_script_file,
local.dhcp_containerfile_file,
local.dhcp_container_file,
]
dhcp_systemd_units = [
local.dhcp_config_path_systemd_unit,
local.dhcp_config_service_systemd_unit,
]
}

View file

@ -0,0 +1,16 @@
:80 {
handle_path /isos/* {
root * /srv/isos
file_server
}
handle_path /config/* {
root * /srv/config/writable
file_server
}
handle_path /ipxe/* {
root * /srv/ipxe
file_server
}
error * 404
log
}

View file

@ -0,0 +1,5 @@
FROM docker.io/caddy:2.8
COPY Caddyfile /etc/caddy/
RUN mkdir -p /srv/ipxe
COPY ipxe.script /srv/ipxe/
EXPOSE 80

View file

@ -0,0 +1,22 @@
[Unit]
Description = HTTP Server (Caddy)
Wants = network-online.target
After = network-online.target
Wants=ign_files_init.service
After=ign_files_init.service
[Container]
ContainerName = caddy
Image=localhost/caddy:2.8
Volume=caddy_data.volume:/data:z
Volume=fcos_images.volume:/srv/isos:ro,z
Volume=ign_files.volume:/srv/config:ro,z
PublishPort=80:80/tcp
[Service]
WorkingDirectory=/var/roothome/caddy/
ExecStartPre=/usr/bin/podman build -t caddy:2.8 .
Restart=on-failure
[Install]
WantedBy=multi-user.target

View file

@ -0,0 +1,8 @@
[Unit]
Description = Caddy Data Volume
[Volume]
VolumeName = caddy_data
Device=/dev/disk/by-label/caddy_data
Options=nodev,noexec,nosuid,rootcontext=system_u:object_r:container_file_t:s0
Type=ext4

View file

@ -0,0 +1,8 @@
[Unit]
Description = FCOS Image Volume
[Volume]
VolumeName = fcos_images
Device=/dev/disk/by-label/fcos_images
Options=nodev,noexec,nosuid,rootcontext=system_u:object_r:container_file_t:s0
Type=ext4

View file

@ -0,0 +1,12 @@
[Unit]
Description = Download Latest FCOS Image
[Container]
ContainerName = fcos_downloader
Image = image_downloader.image
Exec = download -s stable -f pxe
Volume = fcos_images.volume:/data:z
WorkingDir = /data
[Install]
WantedBy=multi-user.target

View file

@ -0,0 +1,9 @@
[Unit]
Description = FCOS Container Image
Wants=network-online.target
After=network-online.target
[Image]
Image = quay.io/coreos/coreos-installer:release
TLSVerify = true

View file

@ -0,0 +1,13 @@
#!ipxe
set BASEURL ${128.1:string}
set CONFIGURL ${BASEURL}config/${128.2:string}
set INSTALLDEV ${128.3:string}
set KERNEL ${BASEURL}isos/${129.1:string}
set INITRAMFS ${BASEURL}isos/${129.2:string}
set ROOTFS ${BASEURL}isos/${129.3:string}
kernel ${KERNEL} initrd=main coreos.live.rootfs_url=${ROOTFS} coreos.inst.install_dev=${INSTALLDEV} coreos.inst.ignition_url=${CONFIGURL}
initrd --name main ${INITRAMFS}
boot

View file

@ -0,0 +1,9 @@
[Unit]
Description = Path Monitor for DHCP Config
[Path]
PathChanged=${path}
TriggerLimitIntervalSec=0
[Install]
WantedBy=multi-user.target

View file

@ -0,0 +1,6 @@
[Unit]
Description = Restart DNSMasq Service
[Service]
Type=oneshot
ExecStart=/usr/bin/systemctl restart dnsmasq_container.service

View file

@ -0,0 +1,8 @@
[Unit]
Description = DHCP Data Volume
[Volume]
VolumeName = dhcp_data
Device=/dev/disk/by-label/dhcp_data
Options=nodev,noexec,nosuid,rootcontext=system_u:object_r:container_file_t:s0
Type=ext4

View file

@ -0,0 +1,6 @@
FROM docker.io/alpine:3.19.1
RUN apk add dnsmasq
COPY dnsmasq.conf /etc/dnsmasq.conf
COPY dhcp-options /etc/dnsmasq.options
EXPOSE 67/udp
ENTRYPOINT ["/usr/sbin/dnsmasq", "--conf-file=/etc/dnsmasq.conf"]

View file

@ -0,0 +1,22 @@
conf-file=/etc/dnsmasq.options
conf-dir=${config_extension_dir},*.conf
port=0 # disables DNS feature
keep-in-foreground # keep in foreground to prevent exit of the container
dhcp-range=${dhcp_range},static,${dhcp_range_netmask}
dhcp-option=option:router,${dhcp_router}
dhcp-option=encap:128,1,"http://${dhcp_server_ip_addr}/"
dhcp-boot=http://${dhcp_server_ip_addr}/ipxe/ipxe.script
dhcp-lease-max=150
dhcp-leasefile=/data/dnsmasq.leases
log-dhcp
#Host Example
#dhcp-host=,id:sample,set:sampletag,10.109.0.10,sample
#dhcp-option=tag:sampletag,encap:128,2,"sample.ign"
#dhcp-option=tag:sampletag,encap:128,3,"/dev/disk/by-path/0000:00:00.0"

View file

@ -0,0 +1,27 @@
[Unit]
Description = DHCP Container
Wants=image_downloader.service
After=image_downloader.service
Wants=network-online.target
After=network-online.target
Wants=dhcp_config_init.service
After=dhcp_config_init.service
[Container]
ContainerName = dnsmasq_container
Image = localhost/dnsmasq:latest
Volume = dhcp_config.volume:/etc/dnsmasq.d:z
Volume = dhcp_data.volume:/data:z
Volume = /dev/log:/dev/log
Network = host
AddCapability = CAP_NET_ADMIN,CAP_NET_RAW
[Service]
WorkingDirectory=/var/roothome/dhcp
ExecStartPre=/bin/bash /var/roothome/generate_dhcp_options.sh
ExecStartPre=/usr/bin/podman build -t dnsmasq:latest .
Restart=on-failure
[Install]
WantedBy=multi-user.target

View file

@ -0,0 +1,26 @@
#!/bin/bash
set -o errexit -o nounset -o pipefail -o xtrace
declare -r MOUNTDIR="$(mktemp -d)"
cleanstep1() {
rm -r "${MOUNTDIR}"
}
trap cleanstep1 EXIT
mount -t ext4 -o noexec,nosuid,nodev,rootcontext=system_u:object_r:container_file_t:s0 LABEL=fcos_images "${MOUNTDIR}"
cleanstep2() {
umount -f "${MOUNTDIR}"
cleanstep1
}
trap cleanstep2 EXIT
declare -r KERNEL_FILE="$(basename "$(ls -1 "${MOUNTDIR}"/fedora-coreos-*-live-kernel-x86_64)")"
declare -r INITRAMFS_FILE="$(basename "$(ls -1 "${MOUNTDIR}"/fedora-coreos-*-live-initramfs.x86_64.img)")"
declare -r ROOTFS_FILE="$(basename "$(ls -1 "${MOUNTDIR}"/fedora-coreos-*-live-rootfs.x86_64.img)")"
echo "dhcp-option=encap:129,1,\"${KERNEL_FILE}\"" > /root/dhcp/dhcp-options
echo "dhcp-option=encap:129,2,\"${INITRAMFS_FILE}\"" >> /root/dhcp/dhcp-options
echo "dhcp-option=encap:129,3,\"${ROOTFS_FILE}\"" >> /root/dhcp/dhcp-options

View file

@ -0,0 +1,10 @@
[connection]
id=${iface}
type=ethernet
interface-name=${iface}
[ipv4]
address1=${ip_address}/${netmask},${gateway}
dns=${dns_server};
dns-search=
may-fail=false
method=manual

View file

@ -0,0 +1,23 @@
FROM docker.io/alpine:3.19.1
RUN apk add openssh-server bash
COPY sshd_config /etc/ssh/sshd_config
RUN /bin/bash -c "\
%{for idx, chroot_user in chrooted_users ~}
addgroup -g $((2000 + ${idx})) ${chroot_user.username} && \
adduser -D -G ${chroot_user.username} -u $((2000 + ${idx})) ${chroot_user.username} && \
echo '${chroot_user.username}:*' | chpasswd -e && \
mkdir -p ${chroot_user.chroot} && \
chown root:root ${chroot_user.chroot} && \
chmod 0755 ${chroot_user.chroot} && \
mkdir /home/${chroot_user.username}/.ssh && \
chown ${chroot_user.username}:${chroot_user.username} /home/${chroot_user.username}/.ssh && \
chmod 0700 /home/${chroot_user.username}/.ssh && \
touch /home/${chroot_user.username}/.ssh/authorized_keys && \
chown ${chroot_user.username}:${chroot_user.username} /home/${chroot_user.username}/.ssh/authorized_keys && \
chmod 0600 /home/${chroot_user.username}/.ssh/authorized_keys && \
echo '${chroot_user.ssh_public_key}' > /home/${chroot_user.username}/.ssh/authorized_keys && \
%{endfor ~}
:"
EXPOSE 22/tcp
ENTRYPOINT ["/usr/sbin/sshd", "-D", "-f", "/etc/ssh/sshd_config"]

View file

@ -0,0 +1,8 @@
[Unit]
Description = DHCP Config Volume
[Volume]
VolumeName = dhcp_config
Device=/dev/disk/by-label/dhcp_config
Options=nodev,noexec,nosuid,rootcontext=system_u:object_r:container_file_t:s0
Type=ext4

View file

@ -0,0 +1,20 @@
[Unit]
Description = Initialize dhcp_config dirs
Wants=network-online.target
After=network-online.target
[Container]
ContainerName = dhcp_config_init
Image = localhost/sftp:latest
Volume = dhcp_config.volume:/blip:z
Entrypoint = /bin/sh
Exec = -c "mkdir -p /blip/writable ; chown root:terraform_dhcp /blip/writable ; chmod 0775 /blip/writable"
[Service]
Restart=on-failure
WorkingDirectory=/var/roothome/sftp
ExecStartPre=podman build -t sftp:latest .
[Install]
WantedBy=multi-user.target

View file

@ -0,0 +1,8 @@
[Unit]
Description = Ignition File Volume
[Volume]
VolumeName = ign_files
Device=/dev/disk/by-label/ign_files
Options=nodev,noexec,nosuid,rootcontext=system_u:object_r:container_file_t:s0
Type=ext4

View file

@ -0,0 +1,20 @@
[Unit]
Description = Initialize ign_files dirs
Wants=network-online.target
After=network-online.target
[Container]
ContainerName = ign_files_init
Image = localhost/sftp:latest
Volume = ign_files.volume:/blip:z
Entrypoint = /bin/sh
Exec = -c "mkdir -p /blip/writable ; chown root:terraform_ignition /blip/writable ; chmod 0775 /blip/writable"
[Service]
Restart=on-failure
WorkingDirectory=/var/roothome/sftp
ExecStartPre=podman build -t sftp:latest .
[Install]
WantedBy=multi-user.target

View file

@ -0,0 +1,29 @@
[Unit]
Description = SFTP Server
Wants=sftp_init_keys.service
After=sftp_init_keys.service
Wants=network-online.target
After=network-online.target
Wants=dhcp_config_init.service
After=dhcp_config_init.service
Wants=ign_files_init.service
After=ign_files_init.service
[Container]
ContainerName = sftp
Image = localhost/sftp:latest
PublishPort=${external_port}:${internal_port}
Volume = dhcp_config.volume:/data/dhcp_config:z
Volume = ign_files.volume:/data/ign_files:z
Volume = ssh_keys.volume:/data/ssh_keys:z
Volume = /dev/log:/dev/log
[Service]
WorkingDirectory=/var/roothome/sftp
ExecStartPre=podman build -t sftp:latest .
Restart=on-failure
[Install]
WantedBy=multi-user.target

View file

@ -0,0 +1,20 @@
[Unit]
Description = SFTP Key Initialisation
Wants=network-online.target
After=network-online.target
[Container]
ContainerName = sftp_init_keys
Image = localhost/sftp:latest
Volume = ssh_keys.volume:/data/ssh_keys:z
Entrypoint = /bin/sh
Exec = -c "[ -f /data/ssh_keys/ssh_host_ed25519_key ] || ssh-keygen -N '' -f /data/ssh_keys/ssh_host_ed25519_key -t ed25519"
[Service]
WorkingDirectory=/var/roothome/sftp
ExecStartPre=podman build -t sftp:latest .
Restart=on-failure
[Install]
WantedBy=multi-user.target

View file

@ -0,0 +1,8 @@
[Unit]
Description = SSH Keys
[Volume]
VolumeName = ssh_keys
Device=/dev/disk/by-label/ssh_keys
Options=nodev,noexec,nosuid,rootcontext=system_u:object_r:container_file_t:s0
Type=ext4

View file

@ -0,0 +1,263 @@
terraform {
required_providers {
proxmox = {
source = "bpg/proxmox",
version = "~>0.56.1"
}
random = {
source = "hashicorp/random"
}
local = {
source = "hashicorp/local"
}
}
required_version = ">=1.6.2"
}
module "sshd" {
source = "../sshd"
address_family = "inet"
}
locals {
data_device_path = "/dev/disk/by-path/pci-0000:00:0a.0"
data_disk = {
device = local.data_device_path
partitions = [
{
label = "caddy_data"
number = 1
startMiB = 0
sizeMiB = 100
typeGuid = "0FC63DAF-8483-4772-8E79-3D69D8477DE4"
resize = true
},
{
label = "dhcp_config"
number = 2
startMiB = 0
sizeMiB = 10
typeGuid= "0FC63DAF-8483-4772-8E79-3D69D8477DE4"
resize = true
},
{
label = "dhcp_data"
number = 3
startMiB = 0
sizeMiB = 10
typeGuid= "0FC63DAF-8483-4772-8E79-3D69D8477DE4"
resize = true
},
{
label = "fcos_images"
number = 4
startMiB = 0
sizeMiB = 8192
typeGuid= "0FC63DAF-8483-4772-8E79-3D69D8477DE4"
resize = true
},
{
label = "ign_files"
number = 5
startMiB = 0
sizeMiB = 512
typeGuid= "0FC63DAF-8483-4772-8E79-3D69D8477DE4"
resize = true
},
{
label = "ssh_keys"
number = 6
startMiB = 0
sizeMiB = 10
typeGuid= "0FC63DAF-8483-4772-8E79-3D69D8477DE4"
resize = true
}
]
}
hostname_file = {
path = "/etc/hostname"
user = {id = 0}
group = {id = 0}
mode = 420 #0644
contents = {
source = format(
"data:text/plain;base64,%s",
base64encode(var.hostname),
)
}
}
network_config_file = {
path = "/etc/NetworkManager/system-connections/${var.dhcp_iface}.nmconnection"
user = {id = 0}
group = {id = 0}
mode = 384 #0600
contents = {
source = format(
"data:text/plain;base64,%s",
base64encode(templatefile(
"${path.module}/files/dhcp_nmconnection.tftpl",
{
iface = var.dhcp_iface
ip_address = var.dhcp_server_ip_addr
netmask = split("/", var.dhcp_range)[1]
gateway = var.dhcp_gateway
dns_server = var.dhcp_gateway
}
))
)
}
}
core_user = {
name = "core"
passwordHash = "$6$vDMAZf/yOO6mEbcs$6VE7WD8T9/PeotszMFxatOQxB/rFmLDWsNajg4sI0O47OikSuVpqPjkxRbzcueiXn6rBUY1ubCHlp0nnoZ1VI1" # password is "tititoto"; only there for debug; please remove in prod
sshAuthorizedKeys = [
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFQnLSYLGzUVmDMMGgEKCNgfAOkIuqhOMGGuvgskACum fmaury@2a01cb00142b3d00ee15f742996f2775.ipv6.abo.wanadoo.fr"
]
}
ignition_config = jsonencode({
ignition = {
version = "3.4.0"
}
storage = {
disks = [
local.data_disk,
]
filesystems = concat(
local.dhcp_filesystems,
local.caddy_filesystems,
local.sftp_filesystems,
)
directories = concat(
local.dhcp_directories,
local.caddy_directories,
local.sftp_directories,
)
files = concat(
[
local.hostname_file,
local.network_config_file,
],
module.sshd.files,
local.dhcp_files,
local.caddy_files,
local.sftp_files,
)
}
systemd = {
units = concat(
local.dhcp_systemd_units,
local.caddy_systemd_units,
module.sshd.systemd_units,
)
}
passwd = {
users = concat(
[
local.core_user
],
module.sshd.users,
)
groups = module.sshd.groups
}
})
}
resource "random_pet" "config_name" {
length = 4
}
locals {
generated_ignition_config_file = "netboot_server_ignition_config_${random_pet.config_name.id}.ign"
}
resource "local_file" "api_token" {
content = "Authorization: PVEAPIToken=${var.pve_api_token}"
filename = "pve_api_token"
file_permission = "0600"
}
resource "local_file" "netboot_server_ignition_config" {
depends_on = [ local_file.api_token ]
content = local.ignition_config
filename = format("${path.module}/%s", local.generated_ignition_config_file)
file_permission = "0644"
# Download ISO to customize
provisioner "local-exec" {
command = <<EOT
podman run --security-opt label=disable --pull=always --rm -v ${path.cwd}/${path.module}:/data -w /data \
quay.io/coreos/coreos-installer:release download -f iso
EOT
}
# Customize ISO
provisioner "local-exec" {
environment = {
KERNEL_ARG = "--live-karg-append=coreos.liveiso.fromram"
IGNITION_ARG = "--live-ignition=./${local.generated_ignition_config_file}"
}
command = <<EOT
rm -f ${path.module}/customized-${random_pet.config_name.id}.iso && \
podman run --security-opt label=disable --pull=always --rm -v ${path.cwd}/${path.module}:/data -w /data \
quay.io/coreos/coreos-installer:release \
iso customize $KERNEL_ARG $IGNITION_ARG \
-o customized-${random_pet.config_name.id}.iso $(basename $(ls -1 ${path.module}/fedora-coreos-*-live.x86_64.iso))
EOT
}
provisioner "local-exec" {
command = <<EOT
curl \
-F "content=iso" \
-F "filename=@${path.module}/customized-${random_pet.config_name.id}.iso;type=application/vnd.efi.iso;filename=fcos-netboot-server-${random_pet.config_name.id}.iso" \
-H "@${local_file.api_token.filename}" \
"${var.pve_api_base_url}api2/json/nodes/${var.pve_node_name}/storage/${var.pve_storage_id}/upload"
EOT
}
}
resource "proxmox_virtual_environment_vm" "netboot_server" {
name = "netboot-server"
node_name = var.pve_node_name
vm_id = var.pve_vm_id
cpu {
architecture = "x86_64"
type = "host"
sockets = 1
cores = 4
}
memory {
dedicated = 4096
}
cdrom {
enabled = true
file_id = "${var.pve_storage_id}:iso/fcos-netboot-server-${random_pet.config_name.id}.iso"
}
disk {
datastore_id = var.pve_storage_id
interface = "virtio0"
size = 10
}
network_device {
bridge = var.prod_network_name
model = "virtio"
}
operating_system {
type = "l26"
}
keyboard_layout = "fr"
vga {}
serial_device{}
}

View file

View file

@ -0,0 +1,191 @@
module "sftp" {
source = "../sshd"
base_config_dir = "/var/roothome/sftp"
use_socket_activation = true
address_family = "inet"
listen_port = 22
sftp_only = true
chrooted_users = local.chrooted_users
host_keys = ["/data/ssh_keys/ssh_host_ed25519_key"]
}
locals {
sftp_keys_filesystem = {
device = "${local.data_device_path}-part6"
format = "ext4"
label = "ssh_keys"
}
chrooted_users = [
{
username = "terraform_dhcp"
chroot = "/data/dhcp_config"
ssh_public_key = var.ssh_public_key_opentofu_netboot_server
},
{
username = "terraform_ignition"
chroot = "/data/ign_files"
ssh_public_key = var.ssh_public_key_opentofu_netboot_server
}
]
sftp_build_dir = {
path = "/var/roothome/sftp"
user = {id = 0}
group = {id = 0}
mode = 448 # 0700
}
sftp_containerfile_file = {
path = "/var/roothome/sftp/Containerfile"
user = {id = 0}
group = {id = 0}
mode = 420 # 0644
contents = {
source = format(
"data:text/plain;base64,%s",
base64encode(templatefile(
"${path.module}/files/sftp/Containerfile.tftpl",
{
chrooted_users = local.chrooted_users
}
))
)
}
}
sftp_keys_volume_file = {
path = "/etc/containers/systemd/ssh_keys.volume"
user = {id = 0}
group = {id = 0}
mode = 420 # 0644
contents = {
source = format(
"data:text/plain;base64,%s",
base64encode(file("${path.module}/files/sftp/ssh_keys.volume"))
)
}
}
sftp_init_keys_container_file = {
path = "/etc/containers/systemd/sftp_init_keys.container"
user = {id = 0}
group = {id = 0}
mode = 420 # 0644
contents = {
source = format(
"data:text/plain;base64,%s",
base64encode(file("${path.module}/files/sftp/sftp_init_keys.container"))
)
}
}
sftp_container_file = {
path = "/etc/containers/systemd/sftp.container"
user = {id = 0}
group = {id = 0}
mode = 420 # 0644
contents = {
source = format(
"data:text/plain;base64,%s",
base64encode(templatefile(
"${path.module}/files/sftp/sftp.container.tftpl",
{
internal_port = 22
external_port = 2222
}
))
)
}
}
dhcp_config_filesystem = {
device = "${local.data_device_path}-part2"
format = "ext4"
label = "dhcp_config"
}
sftp_dhcp_config_init_container = {
path = "/etc/containers/systemd/dhcp_config_init.container"
user = {id = 0}
group = {id = 0}
mode = 420 # 0644
contents = {
source = format(
"data:text/plain;base64,%s",
base64encode(file("${path.module}/files/sftp/dhcp_config_init.container"))
)
}
}
sftp_dhcp_config_volume_file = {
path = "/etc/containers/systemd/dhcp_config.volume"
user = {id = 0}
group = {id = 0}
mode = 420 # 0644
contents = {
source = format(
"data:text/plain;base64,%s",
base64encode(file("${path.module}/files/sftp/dhcp_config.volume"))
)
}
}
ignition_files_filesystem = {
device = "${local.data_device_path}-part5"
format = "ext4"
label = "ign_files"
}
sftp_ignition_files_init_container = {
path = "/etc/containers/systemd/ign_files_init.container"
user = {id = 0}
group = {id = 0}
mode = 420 # 0644
contents = {
source = format(
"data:text/plain;base64,%s",
base64encode(file("${path.module}/files/sftp/ign_files_init.container"))
)
}
}
sftp_ignition_files_volume_file = {
path = "/etc/containers/systemd/ign_files.volume"
user = {id = 0}
group = {id = 0}
mode = 420 # 0644
contents = {
source = format(
"data:text/plain;base64,%s",
base64encode(file("${path.module}/files/sftp/ign_files.volume"))
)
}
}
sftp_filesystems = [
local.sftp_keys_filesystem,
local.dhcp_config_filesystem,
local.ignition_files_filesystem,
]
sftp_directories = [
local.sftp_build_dir,
]
sftp_files = concat(
[
local.sftp_keys_volume_file,
local.sftp_init_keys_container_file,
local.sftp_container_file,
local.sftp_containerfile_file,
local.sftp_dhcp_config_init_container,
local.sftp_dhcp_config_volume_file,
local.sftp_ignition_files_init_container,
local.sftp_ignition_files_volume_file,
],
module.sftp.files
)
# we can safely ignore the systemd units and users since all of them go in the container and we already took care of it in the Containerfile
}

View file

@ -0,0 +1,78 @@
variable "pve_api_base_url" {
type = string
nullable = false
}
variable "pve_api_token" {
type = string
nullable = false
sensitive = true
}
variable "pve_node_name" {
type = string
nullable = false
}
variable "pve_storage_id" {
type = string
nullable = false
}
variable "pve_vm_id" {
type = number
nullable = false
}
variable "prod_network_name" {
type = string
nullable = false
}
variable "dhcp_server_ip_addr" {
type = string
nullable = false
validation {
condition = can(cidrnetmask("${var.dhcp_server_ip_addr}/32"))
error_message = "Invalid DHCP server address."
}
}
variable "dhcp_iface" {
type = string
nullable = false
}
variable "dhcp_gateway" {
type = string
nullable = false
validation {
condition = can(cidrnetmask("${var.dhcp_gateway}/32"))
error_message = "Invalid gateway"
}
}
variable "dhcp_range" {
type = string
nullable = false
validation {
condition = can(cidrnetmask(var.dhcp_range))
error_message = "Invalid DHCP range."
}
}
variable "ssh_public_key_opentofu_netboot_server" {
type = string
nullable = false
}
variable "fcos_image_version" {
type = string
nullable = false
default = "40.20240504.3.0"
}
variable "hostname" {
type = string
nullable = false
}

View file

@ -0,0 +1,3 @@
dhcp-host=${mac_address},set:vm_id_${vm_id},${host_ip},${hostname}
dhcp-option=tag:vm_id_${vm_id},encap:128,2,"${vm_id}.ign"
dhcp-option=tag:vm_id_${vm_id},encap:128,3,"/dev/disk/by-path/pci-0000:00:0a.0"

171
modules/poc/main.tf Normal file
View file

@ -0,0 +1,171 @@
terraform {
required_providers {
proxmox = {
source = "bpg/proxmox"
version = "~>0.56.1"
}
}
required_version = ">=1.6.2"
}
locals {
core_user = {
name = "core"
sshAuthorizedKeys = [
var.admin_ssh_public_key
]
}
hostname_file = {
path = "/etc/hostname"
user = {id = 0}
group = {id = 0}
mode = 420 # 0644
contents = {
source = format(
"data:text/plain;base64,%s",
base64encode(var.instance_name)
)
}
}
ignition_configuration = jsonencode({
ignition = {
version = "3.4.0"
}
storage = {
files = [
{
path = "/etc/hostname"
user = {id = 0}
group = {id = 0}
mode = 420 # 0644
contents = {
source = format(
"data:text/plain;base64,%s",
base64encode(var.instance_name)
)
}
},
]
}
passwd = {
users = [
local.core_user
]
}
})
}
resource "random_pet" "config_name" {
length = 4
}
locals {
generated_ignition_config_file = "${path.module}/poc_ignition_config_${random_pet.config_name.id}.ign"
}
resource "local_file" "sftp_script_for_ignition_file" {
content = <<EOT
cd writable
-rm ${var.pve_vm_id}.ign
put ${local.generated_ignition_config_file} ${var.pve_vm_id}.ign
EOT
filename = "${path.module}/poc_sftp_script_for_ignition_config_${random_pet.config_name.id}"
file_permission = "0644"
}
resource "local_file" "poc_ignition_config" {
content = local.ignition_configuration
filename = local.generated_ignition_config_file
file_permission = "0644"
provisioner "local-exec" {
command = <<EOT
sftp -P ${var.netboot_server_sftp_port} \
-o ProxyJump=${var.pve_ssh_user}@${var.pve_ssh_host} \
-b "${path.module}/poc_sftp_script_for_ignition_config_${random_pet.config_name.id}" \
terraform_ignition@${var.netboot_server_ip_address}
EOT
}
lifecycle {
replace_triggered_by = [local_file.sftp_script_for_ignition_file]
}
}
resource "local_file" "sftp_script_for_dhcp_config" {
content = <<EOT
cd writable
-rm ${var.pve_vm_id}.conf
put ${path.module}/poc_dhcp_config_${random_pet.config_name.id}.conf ${var.pve_vm_id}.conf
EOT
filename = "${path.module}/poc_sftp_script_for_dhcp_config_${random_pet.config_name.id}"
file_permission = "0644"
}
resource "local_file" "dhcp_config" {
depends_on = [ local_file.sftp_script_for_dhcp_config ]
content = templatefile(
"${path.module}/files/dhcp_config.conf.tftpl",
{
vm_id = var.pve_vm_id
hostname = var.instance_name
host_ip = cidrhost(var.admin_network.prefix, var.pve_vm_id)
mac_address = var.admin_network.mac_address
}
)
filename = "${path.module}/poc_dhcp_config_${random_pet.config_name.id}.conf"
file_permission = "0644"
provisioner "local-exec" {
command = <<EOT
sftp -P ${var.netboot_server_sftp_port} \
-o ProxyJump=${var.pve_ssh_user}@${var.pve_ssh_host} \
-b "${path.module}/poc_sftp_script_for_dhcp_config_${random_pet.config_name.id}" \
terraform_dhcp@${var.netboot_server_ip_address}
EOT
}
lifecycle {
replace_triggered_by = [local_file.sftp_script_for_dhcp_config]
}
}
resource "proxmox_virtual_environment_vm" "poc" {
name = var.instance_name
node_name = var.pve_node_name
vm_id = var.pve_vm_id
cpu {
architecture = "x86_64"
type = "host"
sockets = 1
cores = 4
}
memory {
dedicated = 4096
}
disk {
datastore_id = var.pve_storage_id
interface = "virtio0"
size = 10
}
network_device {
bridge = var.admin_network.name
model = "virtio"
mac_address = var.admin_network.mac_address
}
boot_order = ["net0"]
operating_system {
type = "l26"
}
vga {}
serial_device{}
}

0
modules/poc/outputs.tf Normal file
View file

View file

@ -0,0 +1,3 @@
dhcp-host=1c:69:7a:ff:ff:01,set:vm_id_110,10.110.0.110,poc
dhcp-option=tag:vm_id_110,encap:128,2,"110.ign"
dhcp-option=tag:vm_id_110,encap:128,3,"/dev/disk/by-path/pci-0000:00:0a.0"

View file

@ -0,0 +1,3 @@
cd writable
-rm 110.conf
put modules/poc/poc_dhcp_config_apparently-morally-valid-quail.conf 110.conf

View file

@ -0,0 +1,3 @@
cd writable
-rm 110.ign
put modules/poc/poc_ignition_config_apparently-morally-valid-quail.ign 110.ign

73
modules/poc/variables.tf Normal file
View file

@ -0,0 +1,73 @@
variable "pve_node_name" {
type = string
nullable = false
}
variable "pve_storage_id" {
type = string
nullable = false