initial
This commit is contained in:
commit
0dd3b5bdfe
50 changed files with 2019 additions and 0 deletions
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
.terraform/
|
||||||
|
.terraform.lock.hcl
|
||||||
|
terraform.tfstate
|
||||||
|
**/*.iso
|
||||||
|
**/*.sig
|
||||||
|
**/*.ign
|
||||||
|
pve_api_token
|
||||||
|
settings.tfvars
|
12
LICENSE.md
Normal file
12
LICENSE.md
Normal 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
7
README.md
Normal 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
59
main.tf
Normal 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
|
||||||
|
}
|
149
modules/netboot_server/caddy.tf
Normal file
149
modules/netboot_server/caddy.tf
Normal 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 = [
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
126
modules/netboot_server/dhcp.tf
Normal file
126
modules/netboot_server/dhcp.tf
Normal 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,
|
||||||
|
]
|
||||||
|
}
|
16
modules/netboot_server/files/caddy/Caddyfile
Normal file
16
modules/netboot_server/files/caddy/Caddyfile
Normal 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
|
||||||
|
}
|
5
modules/netboot_server/files/caddy/caddy.Containerfile
Normal file
5
modules/netboot_server/files/caddy/caddy.Containerfile
Normal 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
|
22
modules/netboot_server/files/caddy/caddy.container
Normal file
22
modules/netboot_server/files/caddy/caddy.container
Normal 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
|
8
modules/netboot_server/files/caddy/caddy_data.volume
Normal file
8
modules/netboot_server/files/caddy/caddy_data.volume
Normal 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
|
8
modules/netboot_server/files/caddy/fcos_images.volume
Normal file
8
modules/netboot_server/files/caddy/fcos_images.volume
Normal 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
|
|
@ -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
|
|
@ -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
|
13
modules/netboot_server/files/caddy/ipxe.script
Normal file
13
modules/netboot_server/files/caddy/ipxe.script
Normal 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
|
9
modules/netboot_server/files/dhcp/dhcp_config.path.tftpl
Normal file
9
modules/netboot_server/files/dhcp/dhcp_config.path.tftpl
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
[Unit]
|
||||||
|
Description = Path Monitor for DHCP Config
|
||||||
|
|
||||||
|
[Path]
|
||||||
|
PathChanged=${path}
|
||||||
|
TriggerLimitIntervalSec=0
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
6
modules/netboot_server/files/dhcp/dhcp_config.service
Normal file
6
modules/netboot_server/files/dhcp/dhcp_config.service
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
[Unit]
|
||||||
|
Description = Restart DNSMasq Service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
ExecStart=/usr/bin/systemctl restart dnsmasq_container.service
|
8
modules/netboot_server/files/dhcp/dhcp_data.volume
Normal file
8
modules/netboot_server/files/dhcp/dhcp_data.volume
Normal 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
|
6
modules/netboot_server/files/dhcp/dnsmasq.Containerfile
Normal file
6
modules/netboot_server/files/dhcp/dnsmasq.Containerfile
Normal 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"]
|
22
modules/netboot_server/files/dhcp/dnsmasq.conf.tftpl
Normal file
22
modules/netboot_server/files/dhcp/dnsmasq.conf.tftpl
Normal 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"
|
||||||
|
|
|
@ -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
|
26
modules/netboot_server/files/dhcp/generate_dhcp_options.sh
Normal file
26
modules/netboot_server/files/dhcp/generate_dhcp_options.sh
Normal 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
|
10
modules/netboot_server/files/dhcp_nmconnection.tftpl
Normal file
10
modules/netboot_server/files/dhcp_nmconnection.tftpl
Normal 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
|
23
modules/netboot_server/files/sftp/Containerfile.tftpl
Normal file
23
modules/netboot_server/files/sftp/Containerfile.tftpl
Normal 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"]
|
8
modules/netboot_server/files/sftp/dhcp_config.volume
Normal file
8
modules/netboot_server/files/sftp/dhcp_config.volume
Normal 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
|
20
modules/netboot_server/files/sftp/dhcp_config_init.container
Normal file
20
modules/netboot_server/files/sftp/dhcp_config_init.container
Normal 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
|
8
modules/netboot_server/files/sftp/ign_files.volume
Normal file
8
modules/netboot_server/files/sftp/ign_files.volume
Normal 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
|
20
modules/netboot_server/files/sftp/ign_files_init.container
Normal file
20
modules/netboot_server/files/sftp/ign_files_init.container
Normal 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
|
29
modules/netboot_server/files/sftp/sftp.container.tftpl
Normal file
29
modules/netboot_server/files/sftp/sftp.container.tftpl
Normal 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
|
20
modules/netboot_server/files/sftp/sftp_init_keys.container
Normal file
20
modules/netboot_server/files/sftp/sftp_init_keys.container
Normal 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
|
8
modules/netboot_server/files/sftp/ssh_keys.volume
Normal file
8
modules/netboot_server/files/sftp/ssh_keys.volume
Normal 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
|
263
modules/netboot_server/main.tf
Normal file
263
modules/netboot_server/main.tf
Normal 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{}
|
||||||
|
}
|
0
modules/netboot_server/outputs.tf
Normal file
0
modules/netboot_server/outputs.tf
Normal file
191
modules/netboot_server/sftp.tf
Normal file
191
modules/netboot_server/sftp.tf
Normal 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
|
||||||
|
}
|
78
modules/netboot_server/variables.tf
Normal file
78
modules/netboot_server/variables.tf
Normal 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
|
||||||
|
}
|
3
modules/poc/files/dhcp_config.conf.tftpl
Normal file
3
modules/poc/files/dhcp_config.conf.tftpl
Normal 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
171
modules/poc/main.tf
Normal 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
0
modules/poc/outputs.tf
Normal 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"
|
|
@ -0,0 +1,3 @@
|
||||||
|
cd writable
|
||||||
|
-rm 110.conf
|
||||||
|
put modules/poc/poc_dhcp_config_apparently-morally-valid-quail.conf 110.conf
|
|
@ -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
73
modules/poc/variables.tf
Normal 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
|
||||||
|
}
|
12
modules/sshd/files/sshd.socket
Normal file
12
modules/sshd/files/sshd.socket
Normal 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}
|
73
modules/sshd/files/sshd_config.tftpl
Normal file
73
modules/sshd/files/sshd_config.tftpl
Normal 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
|
1
modules/sshd/files/tmpfiles.conf
Normal file
1
modules/sshd/files/tmpfiles.conf
Normal file
|
@ -0,0 +1 @@
|
||||||
|
d /run/sshd 1700 root root - -
|
128
modules/sshd/main.tf
Normal file
128
modules/sshd/main.tf
Normal 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
20
modules/sshd/outputs.tf
Normal 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
179
modules/sshd/variables.tf
Normal 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
0
outputs.tf
Normal file
14
settings.tfvars
Normal file
14
settings.tfvars
Normal 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
100
variables.tf
Normal 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
|
||||||
|
}
|
Loading…
Reference in a new issue