This commit is contained in:
Florian Maury 2024-06-04 11:25:59 +02:00
commit 2f549a8209
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
LICENCE.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"
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
}
variable "pve_vm_id" {
type = number
nullable = false
}
variable "pve_ssh_user" {
type = string
nullable = false
default = "root"
}
variable "pve_ssh_host" {
type = string
nullable = false
}
variable "netboot_server_ip_address" {
type = string
nullable = false
}
variable "netboot_server_sftp_port" {
type = number
nullable = false
default = 2222
}
variable "instance_name" {
type = string
default = "poc"
}
variable "admin_network" {
type = object({
name = string
prefix = string
mac_address = string
})
nullable = false
}
variable "prod_network" {
type = object({
name = string
prefix = string
mac_address = string
})
nullable = false
}
variable "monitoring_network" {
type = object({
name = string
prefix = string
mac_address = string
})
nullable = false
}
variable "admin_ssh_public_key" {
type = string
nullable = false
}

View file

@ -0,0 +1,12 @@
[Socket]
ListenStream=
%{if length(listen_addresses) > 0}
%{for listen_address in listen_addresses}
ListenStream=${listen_address}:${listen_port}
%{endfor}
%{else}
ListenStream=${listen_port}
%{endif}
%{if listen_unix}
ListenStream=/var/run/sshd/sock
%{endif}

View file

@ -0,0 +1,73 @@
Protocol 2
%{if !use_socket_activation}
AddressFamily ${address_family}
%{for listen_address in listen_addresses}
ListenAddress ${listen_address}
%{endfor}
Port ${listen_port}
%{if listen_unix}
ListenAddress unix:/var/run/sshd/sock
%{endif}
%{endif}
StrictModes yes
UseDNS no
Subsystem sftp internal-sftp
%{if allow_users != ""}
AllowUsers ${allow_users}
%{endif}
%{if allow_groups != ""}
AllowGroups ${allow_groups}
%{endif}
AllowAgentForwarding %{if sftp_only}no%{else}yes%{endif} # According to documentation: Note that disabling agent forwarding does not improve security unless users are also denied shell access, as they can always install their own forwarders.
AllowTcpForwarding %{if allow_tcp_forwarding}yes%{else}no%{endif}
GatewayPorts no
PermitTunnel no
X11Forwarding no
AuthenticationMethods publickey
PubkeyAuthentication yes
KbdInteractiveAuthentication no
# KerberosAuthentication no
PasswordAuthentication no
PermitEmptyPasswords no
PermitRootLogin no
PermitUserEnvironment no
PermitUserRC no
CASignatureAlgorithms ecdsa-sha2-nistp384
Ciphers ${ciphers_algos}
Macs ${macs_algos}
KexAlgorithms ${key_exchange_algos}
HostKeyAlgorithms ${host_key_algorithms}
PubkeyAcceptedAlgorithms ${pub_key_accepted_algorithms}
RekeyLimit ${rekey_limit.size} ${rekey_limit.time}
%{for host_key in host_keys~}
HostKey ${host_key}
%{endfor}
AuthorizedKeysCommand = /usr/libexec/ssh-key-dir %u
AuthorizedKeysCommandUser root
ClientAliveCountMax ${client_alive_count_max}
ClientAliveInterval ${client_alive_interval}
MaxAuthTries ${max_auth_tries}
MaxSessions ${max_sessions}
MaxStartups ${max_startups}
%{for chrooted_user in chrooted_users}
Match User ${chrooted_user.username}
%{if sftp_only}
ForceCommand internal-sftp
%{endif}
ChrootDirectory ${chrooted_user.chroot}
%{endfor}
%{if listen_unix}
Match LocalAddress /var/run/sshd/sock
PermitRootLogin yes
%{endif}
Match all

View file

@ -0,0 +1 @@
d /run/sshd 1700 root root - -

128
modules/sshd/main.tf Normal file
View file

@ -0,0 +1,128 @@
terraform {
required_version = ">=1.6.2"
}
locals {
sshd_config_file = {
path = "${var.base_config_dir}/sshd_config"
overwrite = true
user = {id = 0}
group = {id = 0}
mode = 384 # "0600"
contents = {
source = format(
"data:text/plain;base64,%s",
base64encode(templatefile(
"${path.module}/files/sshd_config.tftpl",
{
use_socket_activation = var.use_socket_activation
listen_unix = var.listen_unix
address_family = var.address_family
listen_addresses = var.listen_addresses
listen_port = var.listen_port
allow_users = join(" ", var.allow_users)
allow_groups = join(" ", var.allow_groups)
sftp_only = tostring(var.sftp_only)
allow_tcp_forwarding = tostring(var.allow_tcp_forwarding)
ciphers_algos = join(",", var.ciphers_algos)
macs_algos = join(",", var.macs_algos)
key_exchange_algos = join(",", var.key_exchange_algos)
host_key_algorithms = join(",", var.host_key_algorithms)
pub_key_accepted_algorithms = join(",", var.pub_key_accepted_algorithms)
host_keys = var.host_keys
rekey_limit = var.rekey_limit
client_alive_count_max = tostring(var.client_alive_count_max)
client_alive_interval = tostring(var.client_alive_interval)
max_auth_tries = tostring(var.max_auth_tries)
max_sessions = tostring(var.max_sessions)
max_startups = tostring(var.max_startup)
chrooted_users = var.chrooted_users
}
))
)
}
}
disable_sshd_socket_systemd_unit = {
name = "sshd.socket"
enabled = false
}
enable_sshd_socket_systemd_unit = {
name = "sshd.socket"
enabled = true
dropins = [
{
name = "listen.conf"
contents = templatefile(
"${path.module}/files/sshd.socket",
{
listen_addresses = var.listen_addresses
listen_port = var.listen_port
listen_unix = var.listen_unix
}
)
}
]
}
unix_socket_tmpfile_file = {
path = "/etc/tmpfiles.d/sshd.conf"
user = {id = 0}
group = {id = 0}
mode = 420 # 0644
contents = {
source = format(
"data:text/plain;base64,%s",
base64encode(file("${path.module}/files/tmpfiles.conf"))
)
}
}
use_unix_socket_files = {
false = []
true = [local.unix_socket_tmpfile_file]
}
disable_sshd_service_systemd_unit = {
name = "sshd.service"
enabled = false
}
enable_sshd_service_systemd_unit = {
name = "sshd.service"
enabled = true
}
systemd_units_on_socket_activation = {
false = [
local.disable_sshd_socket_systemd_unit,
local.enable_sshd_service_systemd_unit,
]
true = [
local.enable_sshd_socket_systemd_unit,
local.disable_sshd_service_systemd_unit,
]
}
chrooted_users = [
for idx, user in var.chrooted_users:
{
name = user.username
uid = 2000 + idx
primaryGroup = user.username
noUserGroup = true
sshAuthorizedKeys = [
user.ssh_public_key
]
}
]
chrooted_groups = [
for idx, user in var.chrooted_users:
{
name = user.username
gid = 2000 + idx
}
]
}

20
modules/sshd/outputs.tf Normal file
View file

@ -0,0 +1,20 @@
output "files" {
value = concat(
[
local.sshd_config_file,
],
local.use_unix_socket_files[var.listen_unix],
)
}
output "systemd_units" {
value = local.systemd_units_on_socket_activation[var.use_socket_activation]
}
output "users" {
value = local.chrooted_users
}
output "groups" {
value = local.chrooted_groups
}

179
modules/sshd/variables.tf Normal file
View file

@ -0,0 +1,179 @@
variable "base_config_dir" {
type = string
nullable = false
default = "/etc/ssh"
}
variable "use_socket_activation" {
type = bool
nullable = false
default = true
}
variable "listen_unix" {
type = bool
nullable = false
default = false
}
variable "address_family" {
type = string
nullable = false
default = "inet6"
validation {
condition = contains(["any", "inet", "inet6"], var.address_family)
error_message = "Invalid address family."
}
}
variable "listen_addresses" {
type = list(string)
nullable = false
default = []
validation {
condition = length(var.listen_addresses) == 0 || alltrue([
for listen_address in var.listen_addresses:
can(cidrnetmask("${listen_address}/32")) || can(cidrnetmask("${listen_address}/128"))
])
error_message = "Invalid address."
}
}
variable "listen_port" {
type = number
nullable = false
default = 22
validation {
condition = var.listen_port > 0 && var.listen_port < 65536
error_message = "Invalid port."
}
}
variable "ciphers_algos" {
type = list(string)
nullable = false
default = ["chacha20-poly1305@openssh.com"]
}
variable "macs_algos" {
type = list(string)
nullable = false
default = ["hmac-sha2-512-etm@openssh.com"]
}
variable "key_exchange_algos" {
type = list(string)
nullable = false
default = ["sntrup761x25519-sha512@openssh.com", "curve25519-sha256"]
}
variable "host_key_algorithms" {
type = list(string)
nullable = false
default = ["ssh-ed25519"]
}
variable "host_keys" {
type = list(string)
nullable = false
default = []
}
variable "pub_key_accepted_algorithms" {
type = list(string)
nullable = false
default = ["ssh-ed25519"]
}
variable "rekey_limit" {
type = object({
size = string
time = string
})
nullable = false
default = {
size = "1G"
time = "1H"
}
}
variable "allow_users" {
type = list(string)
nullable = false
default = []
}
variable "allow_groups" {
type = list(string)
nullable = false
default = []
}
variable "sftp_only" {
type = bool
nullable = false
default = true
}
variable "allow_tcp_forwarding" {
type = bool
nullable = false
default = false
}
variable "chrooted_users" {
type = list(object({
username = string
chroot = string
ssh_public_key = string
}))
nullable = false
default = []
}
variable "client_alive_count_max" {
type = number
nullable = false
default = 6
validation {
condition = var.client_alive_count_max > 0
error_message = "Invalid Client Alive Count Max."
}
}
variable "client_alive_interval" {
type = number
nullable = false
default = 10
validation {
condition = var.client_alive_interval > 0
error_message = "Invalid Client Alive Interval."
}
}
variable "max_auth_tries" {
type = number
nullable = false
default = 10
validation {
condition = var.max_auth_tries > 2
error_message = "Invalid or insufficient Max Auth Tries."
}
}
variable "max_sessions" {
type = number
nullable = false
default = 10
validation {
condition = var.max_sessions >= 0
error_message = "Invalid or insufficient Max Sessions."
}
}
variable "max_startup" {
type = string
nullable = false
default = "100:70:1000"
}

0
outputs.tf Normal file
View file

14
settings.tfvars Normal file
View file

@ -0,0 +1,14 @@
pve_api_base_url = "https://proxmox.broken-by-design.fr:8006/"
pve_node_name = "ns3152888"
pve_storage_id = "local"
pve_ssh_user = "root"
pve_ssh_host = "proxmox.broken-by-design.fr"
ssh_public_key_opentofu_netboot_server = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFQnLSYLGzUVmDMMGgEKCNgfAOkIuqhOMGGuvgskACum fmaury@fedora-home-1"
ssh_public_key_admin_netboot_server = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFQnLSYLGzUVmDMMGgEKCNgfAOkIuqhOMGGuvgskACum fmaury@fedora-home-1"
admin_network_name = "admin"
admin_network_prefix = "10.110.0.0/24"
prod_network_name = "prod"
prod_network_prefix = "10.109.0.0/24"
monit_network_name = "monit"
monit_network_prefix = "10.111.0.0/24"

100
variables.tf Normal file
View file

@ -0,0 +1,100 @@
variable "pve_api_base_url" {
description = "API URL to the Proxmox cluster"
type = string
nullable = false
}
variable "pve_api_token" {
description = "API token used to connect to the Proxmox cluster"
type = string
nullable = false
sensitive = true
}
variable "pve_node_name" {
description = "Name of the Proxmox node on which files and VMs should be created"
type = string
nullable = false
}
variable "pve_storage_id" {
description = "Name of the Proxmox Storage on which files (ISOs) and VM disks should be created"
type = string
nullable = false
}
variable "pve_ssh_user" {
description = "User used to connect with SSH to the hypervisor to port-forward to the netboot server"
type = string
nullable = false
}
variable "pve_ssh_host" {
description = "Address of the hypervisor to connect to to port-forward to the netboot server"
type = string
nullable = false
}
variable "admin_network_name" {
description = "Admin Network Name"
type = string
nullable = false
default = "admin"
}
variable "admin_network_prefix" {
description = "Network prefix associated with the Admin network"
type = string
nullable = false
validation {
condition = can(cidrnetmask(var.admin_network_prefix))
error_message = "Invalid Admin network prefix"
}
}
variable "prod_network_name" {
description = "Production Network Name"
type = string
nullable = false
default = "prod"
}
variable "prod_network_prefix" {
description = "Network prefix associated with the prod network"
type = string
nullable = false
validation {
condition = can(cidrnetmask(var.prod_network_prefix))
error_message = "Invalid Prod network prefix"
}
}
variable "monit_network_name" {
description = "Monitoring Network Name"
type = string
nullable = false
default = "monit"
}
variable "monit_network_prefix" {
description = "Network prefix associated with the monit network"
type = string
nullable = false
validation {
condition = can(cidrnetmask(var.monit_network_prefix))
error_message = "Invalid monitoring network prefix"
}
}
variable "ssh_public_key_opentofu_netboot_server" {
description = "SSH public key used by Opentofu to connect to the terraform_dhcp and terraform_ignition SFTP accounts"
type = string
nullable = false
}
variable "ssh_public_key_admin_netboot_server" {
description = "SSH public key used to connect to the core account on the netboot_server instance"
type = string
nullable = false
}