1 parent
d92805786b
commit
ee0a1766f4
21 files changed
+1503
-418
No files matched your search
@@ -0,0 +1,5 @@
|
||||
* text=auto eol=lf
|
||||
*.sh text eol=lf
|
||||
*.tf text eol=lf
|
||||
*.tftpl text eol=lf
|
||||
*.yml text eol=lf
|
||||
@@ -0,0 +1,112 @@
|
||||
name: Terraform - deployer la VM Ubuntu
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
vm:
|
||||
description: "Nom de la VM, tel que le fichier vms/<nom>.tfvars"
|
||||
type: string
|
||||
required: true
|
||||
default: ubuntu-01
|
||||
action:
|
||||
description: "plan pour vérifier, apply pour créer ou mettre à jour la VM"
|
||||
type: choice
|
||||
required: true
|
||||
default: plan
|
||||
options:
|
||||
- plan
|
||||
- apply
|
||||
|
||||
concurrency:
|
||||
group: terraform-proxmox-ubuntu-24-04
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
TF_VERSION: "1.9.8"
|
||||
TF_IN_AUTOMATION: "true"
|
||||
TF_INPUT: "false"
|
||||
OPENBAO_ROLE_ID: ${{ secrets.OPENBAO_ROLE_ID }}
|
||||
OPENBAO_SECRET_ID: ${{ secrets.OPENBAO_SECRET_ID }}
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Récupérer le dépôt
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Installer les outils
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if ! command -v jq >/dev/null || ! command -v unzip >/dev/null; then
|
||||
apt-get update -qq
|
||||
apt-get install -y -qq curl jq unzip
|
||||
fi
|
||||
curl -fsSL -o /tmp/terraform.zip \
|
||||
"https://releases.hashicorp.com/terraform/${TF_VERSION}/terraform_${TF_VERSION}_linux_amd64.zip"
|
||||
unzip -oq /tmp/terraform.zip -d /usr/local/bin
|
||||
terraform version
|
||||
|
||||
- name: Charger les secrets depuis OpenBao
|
||||
run: ./scripts/openbao-env.sh
|
||||
|
||||
- name: Initialiser le backend S3
|
||||
run: terraform init
|
||||
|
||||
- name: Sélectionner l'espace de travail de la VM
|
||||
run: |
|
||||
set -euo pipefail
|
||||
test -f "vms/${{ inputs.vm }}.tfvars" || {
|
||||
echo "vms/${{ inputs.vm }}.tfvars n'existe pas. VM déclarées :"
|
||||
ls vms/*.tfvars | xargs -n1 basename
|
||||
exit 1
|
||||
}
|
||||
terraform workspace select -or-create "${{ inputs.vm }}"
|
||||
|
||||
- name: Calculer le plan
|
||||
run: terraform plan -var-file="vms/${{ inputs.vm }}.tfvars" -lock-timeout=120s -out=tfplan
|
||||
|
||||
- name: Résumé du plan
|
||||
run: terraform show -no-color tfplan | tail -n 40
|
||||
|
||||
- name: Appliquer le plan
|
||||
if: ${{ inputs.action == 'apply' }}
|
||||
run: terraform apply -lock-timeout=120s -auto-approve tfplan
|
||||
|
||||
- name: Afficher le point d'entrée
|
||||
if: ${{ inputs.action == 'apply' }}
|
||||
run: |
|
||||
echo "VM : $(terraform output -raw vm_fqdn) ($(terraform output -raw vm_ipv4_address))"
|
||||
echo "SSH : $(terraform output -raw ssh_command)"
|
||||
echo "cloud-init tourne encore 3 à 6 minutes après la fin de ce job."
|
||||
|
||||
- name: Extraire le mot de passe root
|
||||
if: ${{ inputs.action == 'apply' }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
umask 077
|
||||
terraform output -raw root_password > mot-de-passe-root.txt
|
||||
echo "Mot de passe root publie en artefact : a deposer dans Vaultwarden, puis supprimer l artefact."
|
||||
|
||||
- name: Publier le mot de passe root
|
||||
if: ${{ inputs.action == 'apply' }}
|
||||
continue-on-error: true
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: mot-de-passe-root-${{ inputs.vm }}
|
||||
path: mot-de-passe-root.txt
|
||||
retention-days: 1
|
||||
|
||||
- name: Effacer la copie locale du mot de passe
|
||||
if: ${{ always() }}
|
||||
run: rm -f mot-de-passe-root.txt
|
||||
|
||||
- name: Publier l'inventaire Ansible
|
||||
if: ${{ inputs.action == 'apply' }}
|
||||
continue-on-error: true
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: inventaire-ansible-${{ inputs.vm }}
|
||||
path: generated/
|
||||
retention-days: 7
|
||||
@@ -0,0 +1,70 @@
|
||||
name: Terraform - detruire la VM Ubuntu
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
vm:
|
||||
description: "Nom de la VM à détruire, tel que le fichier vms/<nom>.tfvars"
|
||||
type: string
|
||||
required: true
|
||||
default: ubuntu-01
|
||||
confirmation:
|
||||
description: "Saisir DETRUIRE pour confirmer la suppression de la VM et de son disque"
|
||||
type: string
|
||||
required: true
|
||||
|
||||
concurrency:
|
||||
group: terraform-proxmox-ubuntu-24-04
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
TF_VERSION: "1.9.8"
|
||||
TF_IN_AUTOMATION: "true"
|
||||
TF_INPUT: "false"
|
||||
OPENBAO_ROLE_ID: ${{ secrets.OPENBAO_ROLE_ID }}
|
||||
OPENBAO_SECRET_ID: ${{ secrets.OPENBAO_SECRET_ID }}
|
||||
|
||||
jobs:
|
||||
destroy:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Vérifier la confirmation
|
||||
run: |
|
||||
if [ "${{ inputs.confirmation }}" != "DETRUIRE" ]; then
|
||||
echo "Confirmation absente ou incorrecte : rien n'a été détruit."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Récupérer le dépôt
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Installer les outils
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if ! command -v jq >/dev/null || ! command -v unzip >/dev/null; then
|
||||
apt-get update -qq
|
||||
apt-get install -y -qq curl jq unzip
|
||||
fi
|
||||
curl -fsSL -o /tmp/terraform.zip \
|
||||
"https://releases.hashicorp.com/terraform/${TF_VERSION}/terraform_${TF_VERSION}_linux_amd64.zip"
|
||||
unzip -oq /tmp/terraform.zip -d /usr/local/bin
|
||||
terraform version
|
||||
|
||||
- name: Charger les secrets depuis OpenBao
|
||||
run: ./scripts/openbao-env.sh
|
||||
|
||||
- name: Initialiser le backend S3
|
||||
run: terraform init
|
||||
|
||||
- name: Sélectionner l'espace de travail de la VM
|
||||
run: |
|
||||
set -euo pipefail
|
||||
test -f "vms/${{ inputs.vm }}.tfvars" || {
|
||||
echo "vms/${{ inputs.vm }}.tfvars n'existe pas."
|
||||
exit 1
|
||||
}
|
||||
terraform workspace select "${{ inputs.vm }}"
|
||||
|
||||
- name: Détruire
|
||||
run: terraform destroy -var-file="vms/${{ inputs.vm }}.tfvars" -lock-timeout=120s -auto-approve
|
||||
@@ -0,0 +1,63 @@
|
||||
name: Terraform - plan
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [main]
|
||||
push:
|
||||
branches: [main]
|
||||
|
||||
# Un seul job Terraform à la fois : l'état est partagé, autant ne pas se battre
|
||||
# avec le verrou du backend.
|
||||
concurrency:
|
||||
group: terraform-proxmox-ubuntu-24-04
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
TF_VERSION: "1.9.8"
|
||||
TF_IN_AUTOMATION: "true"
|
||||
TF_INPUT: "false"
|
||||
OPENBAO_ROLE_ID: ${{ secrets.OPENBAO_ROLE_ID }}
|
||||
OPENBAO_SECRET_ID: ${{ secrets.OPENBAO_SECRET_ID }}
|
||||
|
||||
jobs:
|
||||
plan:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Récupérer le dépôt
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Installer les outils
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if ! command -v jq >/dev/null || ! command -v unzip >/dev/null; then
|
||||
apt-get update -qq
|
||||
apt-get install -y -qq curl jq unzip
|
||||
fi
|
||||
curl -fsSL -o /tmp/terraform.zip \
|
||||
"https://releases.hashicorp.com/terraform/${TF_VERSION}/terraform_${TF_VERSION}_linux_amd64.zip"
|
||||
unzip -oq /tmp/terraform.zip -d /usr/local/bin
|
||||
terraform version
|
||||
|
||||
- name: Charger les secrets depuis OpenBao
|
||||
run: ./scripts/openbao-env.sh
|
||||
|
||||
- name: Vérifier le formatage
|
||||
run: terraform fmt -check -recursive -diff
|
||||
|
||||
- name: Initialiser le backend S3
|
||||
run: terraform init
|
||||
|
||||
- name: Valider la configuration
|
||||
run: terraform validate
|
||||
|
||||
- name: Calculer le plan de chaque VM déclarée
|
||||
run: |
|
||||
set -euo pipefail
|
||||
for fichier in vms/*.tfvars; do
|
||||
vm=$(basename "$fichier" .tfvars)
|
||||
echo "::group::$vm"
|
||||
terraform workspace select -or-create "$vm"
|
||||
terraform plan -var-file="$fichier" -lock-timeout=120s
|
||||
echo "::endgroup::"
|
||||
done
|
||||
+16
-416
@@ -1,416 +1,16 @@
|
||||
# ---> VisualStudio
|
||||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
##
|
||||
## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
|
||||
|
||||
# User-specific files
|
||||
*.rsuser
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
|
||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||
*.userprefs
|
||||
|
||||
# Mono auto generated files
|
||||
mono_crash.*
|
||||
|
||||
# Build results
|
||||
[Dd]ebug/
|
||||
[Dd]ebugPublic/
|
||||
[Rr]elease/
|
||||
[Rr]eleases/
|
||||
x64/
|
||||
x86/
|
||||
[Ww][Ii][Nn]32/
|
||||
[Aa][Rr][Mm]/
|
||||
[Aa][Rr][Mm]64/
|
||||
bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
[Ll]og/
|
||||
[Ll]ogs/
|
||||
|
||||
# Visual Studio 2015/2017 cache/options directory
|
||||
.vs/
|
||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||
#wwwroot/
|
||||
|
||||
# Visual Studio 2017 auto generated files
|
||||
Generated\ Files/
|
||||
|
||||
# MSTest test Results
|
||||
[Tt]est[Rr]esult*/
|
||||
[Bb]uild[Ll]og.*
|
||||
|
||||
# NUnit
|
||||
*.VisualState.xml
|
||||
TestResult.xml
|
||||
nunit-*.xml
|
||||
|
||||
# Build Results of an ATL Project
|
||||
[Dd]ebugPS/
|
||||
[Rr]eleasePS/
|
||||
dlldata.c
|
||||
|
||||
# Benchmark Results
|
||||
BenchmarkDotNet.Artifacts/
|
||||
|
||||
# .NET Core
|
||||
project.lock.json
|
||||
project.fragment.lock.json
|
||||
artifacts/
|
||||
|
||||
# ASP.NET Scaffolding
|
||||
ScaffoldingReadMe.txt
|
||||
|
||||
# StyleCop
|
||||
StyleCopReport.xml
|
||||
|
||||
# Files built by Visual Studio
|
||||
*_i.c
|
||||
*_p.c
|
||||
*_h.h
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
*.iobj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.ipdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
# but not Directory.Build.rsp, as it configures directory-level build defaults
|
||||
!Directory.Build.rsp
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.tmp_proj
|
||||
*_wpftmp.csproj
|
||||
*.log
|
||||
*.tlog
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
.builds
|
||||
*.pidb
|
||||
*.svclog
|
||||
*.scc
|
||||
|
||||
# Chutzpah Test files
|
||||
_Chutzpah*
|
||||
|
||||
# Visual C++ cache files
|
||||
ipch/
|
||||
*.aps
|
||||
*.ncb
|
||||
*.opendb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
*.cachefile
|
||||
*.VC.db
|
||||
*.VC.VC.opendb
|
||||
|
||||
# Visual Studio profiler
|
||||
*.psess
|
||||
*.vsp
|
||||
*.vspx
|
||||
*.sap
|
||||
|
||||
# Visual Studio Trace Files
|
||||
*.e2e
|
||||
|
||||
# TFS 2012 Local Workspace
|
||||
$tf/
|
||||
|
||||
# Guidance Automation Toolkit
|
||||
*.gpState
|
||||
|
||||
# ReSharper is a .NET coding add-in
|
||||
_ReSharper*/
|
||||
*.[Rr]e[Ss]harper
|
||||
*.DotSettings.user
|
||||
|
||||
# TeamCity is a build add-in
|
||||
_TeamCity*
|
||||
|
||||
# DotCover is a Code Coverage Tool
|
||||
*.dotCover
|
||||
|
||||
# AxoCover is a Code Coverage Tool
|
||||
.axoCover/*
|
||||
!.axoCover/settings.json
|
||||
|
||||
# Coverlet is a free, cross platform Code Coverage Tool
|
||||
coverage*.json
|
||||
coverage*.xml
|
||||
coverage*.info
|
||||
|
||||
# Visual Studio code coverage results
|
||||
*.coverage
|
||||
*.coveragexml
|
||||
|
||||
# NCrunch
|
||||
_NCrunch_*
|
||||
.*crunch*.local.xml
|
||||
nCrunchTemp_*
|
||||
|
||||
# MightyMoose
|
||||
*.mm.*
|
||||
AutoTest.Net/
|
||||
|
||||
# Web workbench (sass)
|
||||
.sass-cache/
|
||||
|
||||
# Installshield output folder
|
||||
[Ee]xpress/
|
||||
|
||||
# DocProject is a documentation generator add-in
|
||||
DocProject/buildhelp/
|
||||
DocProject/Help/*.HxT
|
||||
DocProject/Help/*.HxC
|
||||
DocProject/Help/*.hhc
|
||||
DocProject/Help/*.hhk
|
||||
DocProject/Help/*.hhp
|
||||
DocProject/Help/Html2
|
||||
DocProject/Help/html
|
||||
|
||||
# Click-Once directory
|
||||
publish/
|
||||
|
||||
# Publish Web Output
|
||||
*.[Pp]ublish.xml
|
||||
*.azurePubxml
|
||||
# Note: Comment the next line if you want to checkin your web deploy settings,
|
||||
# but database connection strings (with potential passwords) will be unencrypted
|
||||
*.pubxml
|
||||
*.publishproj
|
||||
|
||||
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||
# in these scripts will be unencrypted
|
||||
PublishScripts/
|
||||
|
||||
# NuGet Packages
|
||||
*.nupkg
|
||||
# NuGet Symbol Packages
|
||||
*.snupkg
|
||||
# The packages folder can be ignored because of Package Restore
|
||||
**/[Pp]ackages/*
|
||||
# except build/, which is used as an MSBuild target.
|
||||
!**/[Pp]ackages/build/
|
||||
# Uncomment if necessary however generally it will be regenerated when needed
|
||||
#!**/[Pp]ackages/repositories.config
|
||||
# NuGet v3's project.json files produces more ignorable files
|
||||
*.nuget.props
|
||||
*.nuget.targets
|
||||
|
||||
# Microsoft Azure Build Output
|
||||
csx/
|
||||
*.build.csdef
|
||||
|
||||
# Microsoft Azure Emulator
|
||||
ecf/
|
||||
rcf/
|
||||
|
||||
# Windows Store app package directories and files
|
||||
AppPackages/
|
||||
BundleArtifacts/
|
||||
Package.StoreAssociation.xml
|
||||
_pkginfo.txt
|
||||
*.appx
|
||||
*.appxbundle
|
||||
*.appxupload
|
||||
|
||||
# Visual Studio cache files
|
||||
# files ending in .cache can be ignored
|
||||
*.[Cc]ache
|
||||
# but keep track of directories ending in .cache
|
||||
!?*.[Cc]ache/
|
||||
|
||||
# Others
|
||||
ClientBin/
|
||||
~$*
|
||||
*~
|
||||
*.dbmdl
|
||||
*.dbproj.schemaview
|
||||
*.jfm
|
||||
*.pfx
|
||||
*.publishsettings
|
||||
orleans.codegen.cs
|
||||
|
||||
# Including strong name files can present a security risk
|
||||
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
||||
#*.snk
|
||||
|
||||
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||
#bower_components/
|
||||
|
||||
# RIA/Silverlight projects
|
||||
Generated_Code/
|
||||
|
||||
# Backup & report files from converting an old project file
|
||||
# to a newer Visual Studio version. Backup files are not needed,
|
||||
# because we have git ;-)
|
||||
_UpgradeReport_Files/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
UpgradeLog*.htm
|
||||
ServiceFabricBackup/
|
||||
*.rptproj.bak
|
||||
|
||||
# SQL Server files
|
||||
*.mdf
|
||||
*.ldf
|
||||
*.ndf
|
||||
|
||||
# Business Intelligence projects
|
||||
*.rdl.data
|
||||
*.bim.layout
|
||||
*.bim_*.settings
|
||||
*.rptproj.rsuser
|
||||
*- [Bb]ackup.rdl
|
||||
*- [Bb]ackup ([0-9]).rdl
|
||||
*- [Bb]ackup ([0-9][0-9]).rdl
|
||||
|
||||
# Microsoft Fakes
|
||||
FakesAssemblies/
|
||||
|
||||
# GhostDoc plugin setting file
|
||||
*.GhostDoc.xml
|
||||
|
||||
# Node.js Tools for Visual Studio
|
||||
.ntvs_analysis.dat
|
||||
node_modules/
|
||||
|
||||
# Visual Studio 6 build log
|
||||
*.plg
|
||||
|
||||
# Visual Studio 6 workspace options file
|
||||
*.opt
|
||||
|
||||
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
||||
*.vbw
|
||||
|
||||
# Visual Studio 6 auto-generated project file (contains which files were open etc.)
|
||||
*.vbp
|
||||
|
||||
# Visual Studio 6 workspace and project file (working project files containing files to include in project)
|
||||
*.dsw
|
||||
*.dsp
|
||||
|
||||
# Visual Studio 6 technical files
|
||||
*.ncb
|
||||
*.aps
|
||||
|
||||
# Visual Studio LightSwitch build output
|
||||
**/*.HTMLClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/ModelManifest.xml
|
||||
**/*.Server/GeneratedArtifacts
|
||||
**/*.Server/ModelManifest.xml
|
||||
_Pvt_Extensions
|
||||
|
||||
# Paket dependency manager
|
||||
.paket/paket.exe
|
||||
paket-files/
|
||||
|
||||
# FAKE - F# Make
|
||||
.fake/
|
||||
|
||||
# CodeRush personal settings
|
||||
.cr/personal
|
||||
|
||||
# Python Tools for Visual Studio (PTVS)
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
# Cake - Uncomment if you are using it
|
||||
# tools/**
|
||||
# !tools/packages.config
|
||||
|
||||
# Tabs Studio
|
||||
*.tss
|
||||
|
||||
# Telerik's JustMock configuration file
|
||||
*.jmconfig
|
||||
|
||||
# BizTalk build output
|
||||
*.btp.cs
|
||||
*.btm.cs
|
||||
*.odx.cs
|
||||
*.xsd.cs
|
||||
|
||||
# OpenCover UI analysis results
|
||||
OpenCover/
|
||||
|
||||
# Azure Stream Analytics local run output
|
||||
ASALocalRun/
|
||||
|
||||
# MSBuild Binary and Structured Log
|
||||
*.binlog
|
||||
|
||||
# NVidia Nsight GPU debugger configuration file
|
||||
*.nvuser
|
||||
|
||||
# MFractors (Xamarin productivity tool) working folder
|
||||
.mfractor/
|
||||
|
||||
# Local History for Visual Studio
|
||||
.localhistory/
|
||||
|
||||
# Visual Studio History (VSHistory) files
|
||||
.vshistory/
|
||||
|
||||
# BeatPulse healthcheck temp database
|
||||
healthchecksdb
|
||||
|
||||
# Backup folder for Package Reference Convert tool in Visual Studio 2017
|
||||
MigrationBackup/
|
||||
|
||||
# Ionide (cross platform F# VS Code tools) working folder
|
||||
.ionide/
|
||||
|
||||
# Fody - auto-generated XML schema
|
||||
FodyWeavers.xsd
|
||||
|
||||
# VS Code files for those working on multiple tools
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
*.code-workspace
|
||||
|
||||
# Local History for Visual Studio Code
|
||||
.history/
|
||||
|
||||
# Windows Installer files from build outputs
|
||||
*.cab
|
||||
*.msi
|
||||
*.msix
|
||||
*.msm
|
||||
*.msp
|
||||
|
||||
# JetBrains Rider
|
||||
*.sln.iml
|
||||
|
||||
# ---> VisualStudioCode
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
!.vscode/*.code-snippets
|
||||
|
||||
# Local History for Visual Studio Code
|
||||
.history/
|
||||
|
||||
# Built Visual Studio Code Extensions
|
||||
*.vsix
|
||||
|
||||
.terraform/
|
||||
*.tfstate
|
||||
*.tfstate.*
|
||||
crash.log
|
||||
terraform.tfvars
|
||||
*.auto.tfvars
|
||||
generated/
|
||||
override.tf
|
||||
override.tf.json
|
||||
backend.hcl
|
||||
tfplan
|
||||
|
||||
# Seules les cles publiques sont versionnees
|
||||
key/*
|
||||
!key/*.pub
|
||||
!key/README.md
|
||||
@@ -0,0 +1,123 @@
|
||||
# CI/CD — dépôt `terraform-proxmox-serveur-ubuntu-24-04`
|
||||
|
||||
La chaîne s'arrête à la création du serveur. Ansible n'est pas déclenché par la CI :
|
||||
il est joué depuis Semaphore, indépendamment.
|
||||
|
||||
## Répartition
|
||||
|
||||
| Étape | Outil | Déclencheur |
|
||||
|---|---|---|
|
||||
| `fmt`, `validate`, `plan` | Gitea Actions | pull request et push sur `main` |
|
||||
| `apply` | Gitea Actions | lancement manuel, `action=apply` |
|
||||
| `destroy` | Gitea Actions | lancement manuel, confirmation littérale `DETRUIRE` |
|
||||
| Configuration applicative | Semaphore | manuel, après la fin de cloud-init |
|
||||
|
||||
## Préalables
|
||||
|
||||
### 1. OpenBao
|
||||
|
||||
Une clé est à **ajouter au secret existant** `secret/proxmox` : le dépôt du snippet
|
||||
cloud-init passe par SSH, le mot de passe du nœud est donc nécessaire.
|
||||
|
||||
```bash
|
||||
bao kv patch secret/proxmox ssh_password='<mot de passe root du nœud pve>'
|
||||
```
|
||||
|
||||
Le secret doit finir par contenir : `api_url`, `api_token`, `node_name`, `ssh_password`.
|
||||
L'adresse du nœud est déduite de `api_url`, il n'y a rien d'autre à saisir.
|
||||
|
||||
Politique et AppRole dédiés au projet, en lecture seule sur les deux secrets utiles :
|
||||
|
||||
```bash
|
||||
bao policy write terraform-proxmox-ubuntu - <<'POLICY'
|
||||
path "secret/data/proxmox" { capabilities = ["read"] }
|
||||
path "secret/metadata/proxmox" { capabilities = ["read"] }
|
||||
path "secret/data/aws" { capabilities = ["read"] }
|
||||
path "secret/metadata/aws" { capabilities = ["read"] }
|
||||
POLICY
|
||||
|
||||
bao write auth/approle/role/terraform-proxmox-ubuntu \
|
||||
token_policies=terraform-proxmox-ubuntu token_ttl=1h token_max_ttl=4h
|
||||
|
||||
bao read auth/approle/role/terraform-proxmox-ubuntu/role-id
|
||||
bao write -f auth/approle/role/terraform-proxmox-ubuntu/secret-id
|
||||
```
|
||||
|
||||
Si vous préférez ne pas multiplier les rôles, le couple `role_id` / `secret_id` déjà
|
||||
utilisé par le dépôt Debian convient tel quel : la politique y donne accès aux deux
|
||||
mêmes secrets, `secret/proxmox` et `secret/aws`.
|
||||
|
||||
### 2. Secrets et variables Gitea
|
||||
|
||||
Gitea ne détient que de quoi ouvrir OpenBao.
|
||||
|
||||
**Secrets du dépôt** (Paramètres → Actions → Secrets) :
|
||||
|
||||
| Nom | Valeur |
|
||||
|---|---|
|
||||
| `OPENBAO_ROLE_ID` | sortie de `role-id` |
|
||||
| `OPENBAO_SECRET_ID` | sortie de `secret-id` |
|
||||
|
||||
Aucune variable de dépôt n'est nécessaire. L'adresse d'OpenBao a une valeur par défaut
|
||||
dans `scripts/openbao-env.sh`, et le backend S3 est écrit en dur dans `state.tf` : ni le
|
||||
bucket ni la région ne sont des secrets.
|
||||
|
||||
La clé d'état, `backend/terraform-proxmox-serveur-ubuntu-24-04/terraform.tfstate`, est
|
||||
**différente** de celle des autres projets. Deux dépôts partageant la même clé
|
||||
écraseraient mutuellement leur état.
|
||||
|
||||
### 3. Bit d'exécution du script
|
||||
|
||||
Le piège déjà rencontré sur `proxmox-create-vm` : le transfert depuis Windows perd le
|
||||
bit d'exécution, et le job échoue sur un « Permission denied ».
|
||||
|
||||
```powershell
|
||||
git update-index --chmod=+x scripts/openbao-env.sh
|
||||
git commit -m "Rendre openbao-env.sh executable"
|
||||
```
|
||||
|
||||
Le fichier `.gitattributes` livré fige les fins de ligne en LF, pour la même raison.
|
||||
|
||||
### 4. Clé publique
|
||||
|
||||
Chaque fichier `vms/<nom>.tfvars` est versionné et référence les clés publiques : vérifier
|
||||
le `ssh-ed25519 AAAA...` d'exemple avant le premier `apply`. Sans quoi la VM sera
|
||||
créée avec une clé inutilisable et il faudra la recréer — cloud-init ne rejoue rien.
|
||||
|
||||
## Fonctionnement des workflows
|
||||
|
||||
`scripts/openbao-env.sh` s'authentifie en AppRole, lit les deux secrets et pousse dans
|
||||
l'environnement du job : `TF_VAR_pve_endpoint`, `TF_VAR_pve_api_token`,
|
||||
`TF_VAR_pve_node`, `TF_VAR_pve_node_address`, `TF_VAR_pve_ssh_password`,
|
||||
`AWS_ACCESS_KEY_ID` et `AWS_SECRET_ACCESS_KEY`. Chaque valeur sensible est passée en
|
||||
`::add-mask::` avant d'être exportée : elle apparaît en `***` si un outil l'affiche.
|
||||
|
||||
Le script s'arrête avec un message explicite quand une clé manque dans OpenBao, plutôt
|
||||
que de laisser Terraform échouer sur une authentification vide.
|
||||
|
||||
`deploy.yml` calcule toujours un plan, et ne l'applique que si `action=apply` — c'est
|
||||
le plan enregistré qui est appliqué, pas un second calcul. À la fin, l'inventaire
|
||||
Ansible est publié en artefact (`inventaire-ansible`), utile surtout comme trace :
|
||||
l'adresse étant fixe, Semaphore peut se contenter d'un inventaire statique.
|
||||
|
||||
## Côté Semaphore
|
||||
|
||||
La configuration Semaphore est décrite dans le dépôt qu'elle concerne :
|
||||
`ansible/CICD.md`.
|
||||
|
||||
## Enchaînement d'un test complet
|
||||
|
||||
1. `deploy.yml` avec `action=apply` — environ 3 minutes.
|
||||
2. Attendre la fin de cloud-init : `ssh [email protected] 'cloud-init status --wait'`.
|
||||
3. Lancer le modèle Semaphore `site.yml` — 10 à 20 minutes.
|
||||
4. Déposer les exports d'Enclume, relancer le modèle `--tags clapi`.
|
||||
5. Pour repartir propre : `destroy.yml` avec la confirmation `DETRUIRE`, puis reprendre
|
||||
à l'étape 1.Aucune variable de dépôt n'est nécessaire : l'adresse d'OpenBao a une valeur par
|
||||
défaut dans `scripts/openbao-env.sh`, et le backend S3 est écrit en dur dans
|
||||
`state.tf`. Ni le bucket ni la région ne sont des secrets.
|
||||
|
||||
La clé d'état, `backend/terraform-proxmox-serveur-ubuntu-24-04/terraform.tfstate`, est
|
||||
**différente** de celle des autres projets : deux dépôts partageant la même clé
|
||||
écraseraient mutuellement leur état.
|
||||
|
||||
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
# Modèle de comptes
|
||||
|
||||
La commande `sudo` est **inutilisable** sur cette machine. L'escalade de privilèges
|
||||
passe par `su -`.
|
||||
|
||||
| Compte | Accès SSH | Privilèges | Usage |
|
||||
|---|---|---|---|
|
||||
| `hcornet` | clé | aucun | administration humaine, puis `su -` |
|
||||
| `invite` | clé | aucun | consultation |
|
||||
| `ansible` | clé | aucun | compte de service, sans droits |
|
||||
| `root` | **clé uniquement** | tous | c'est par là qu'Ansible travaille |
|
||||
|
||||
`PermitRootLogin prohibit-password` : root est joignable par clé, jamais par mot de
|
||||
passe. Le mot de passe root ne sert donc qu'en local — `su -` ou la console Proxmox.
|
||||
|
||||
`AllowUsers` est généré à partir des comptes réellement créés : un compte absent ne
|
||||
peut pas ouvrir de session, même s'il existe sur le système.
|
||||
|
||||
## Pourquoi sudo n'est pas purgé, contrairement à Debian
|
||||
|
||||
Sur Ubuntu, `sudo` est une **dépendance d'`ubuntu-minimal`**. Le purger entraînerait la
|
||||
suppression du métapaquet, et l'`autoremove` qui suit pourrait alors emporter
|
||||
`netplan.io`, `udev` ou `vim-tiny` — de quoi rendre la machine inutilisable.
|
||||
|
||||
Le cloud-init applique donc :
|
||||
|
||||
```bash
|
||||
dpkg-statoverride --update --add root root 0000 /usr/bin/sudo
|
||||
rm -f /etc/sudoers.d/90-cloud-init-users
|
||||
```
|
||||
|
||||
Le binaire perd son bit setuid et tous ses droits : plus personne ne peut l'exécuter.
|
||||
Le paquet reste installé, la dépendance est satisfaite, et `dpkg-statoverride` réapplique
|
||||
ces droits à chaque mise à jour du paquet — c'est précisément sa raison d'être.
|
||||
|
||||
Vérification :
|
||||
|
||||
```bash
|
||||
ls -l /usr/bin/sudo # doit afficher ----------
|
||||
sudo -n true # doit échouer
|
||||
```
|
||||
|
||||
Pour revenir en arrière, si le besoin apparaissait :
|
||||
|
||||
```bash
|
||||
dpkg-statoverride --remove /usr/bin/sudo
|
||||
chmod 4755 /usr/bin/sudo
|
||||
```
|
||||
|
||||
## Où vivent les clés
|
||||
|
||||
Un fichier `.pub` par identité dans `key/`, référencé par les fichiers `vms/*.tfvars`.
|
||||
Ce sont les mêmes clés que dans le dépôt Debian : les recopier évite d'en gérer un
|
||||
second jeu.
|
||||
|
||||
Un compte dont `ssh_key_files` est vide n'est pas créé. Deux garde-fous avant création :
|
||||
un fichier référencé mais absent fait échouer le plan, et une valeur qui n'est pas une
|
||||
clé publique est refusée par une précondition.
|
||||
|
||||
`root_ssh_key_files` doit contenir la clé que Semaphore présente, sans quoi le playbook
|
||||
se verra refuser la connexion après chaque recréation.
|
||||
|
||||
## Le mot de passe root
|
||||
|
||||
Généré par Terraform à la création, en 24 caractères, et publié en artefact
|
||||
`mot-de-passe-root-<vm>` par le workflow `deploy`, avec une rétention d'un jour :
|
||||
le télécharger, le déposer dans Vaultwarden, puis supprimer l'artefact.
|
||||
|
||||
Il est conservé en clair dans l'état Terraform sur S3 — contrepartie de la génération
|
||||
automatique.
|
||||
|
||||
## Côté Ansible
|
||||
|
||||
La machine n'ayant pas de `sudo` utilisable, un playbook doit s'y connecter
|
||||
directement en root : `remote_user: root` et `become: false`, comme dans le dépôt
|
||||
`ansible-centreon`. L'inventaire généré par Terraform pointe déjà sur `root`.
|
||||
@@ -1,3 +1,65 @@
|
||||
# terraform-proxmox-serveur-ubuntu
|
||||
# terraform-proxmox-serveur-ubuntu-24-04
|
||||
|
||||
Installation d'un serveur Ubuntu sur proxmox
|
||||
Crée sur le nœud Proxmox `pve` une VM Ubuntu Server 24.04 LTS réduite au strict
|
||||
nécessaire, durcie par cloud-init, et prête à être reprise par Ansible.
|
||||
|
||||
Jumeau du dépôt `terraform-proxmox-serveur-debian-12` : même structure, mêmes
|
||||
conventions, même CI/CD. Seules la distribution et les quelques points où elle diffère
|
||||
de Debian ont changé.
|
||||
|
||||
## Ce que le code met en place
|
||||
|
||||
| Point | Choix retenu |
|
||||
|---|---|
|
||||
| Système | Ubuntu Server 24.04 LTS *cloudimg* (image officielle Canonical) |
|
||||
| Comptes | comptes unix sans mot de passe, sudo NOPASSWD **absent**, clé publique uniquement |
|
||||
| root | joignable en SSH par clé seulement, mot de passe réservé à `su -` et à la console |
|
||||
| sudo | neutralisé par `dpkg-statoverride` (voir [COMPTES.md](COMPTES.md)) |
|
||||
| Services exposés | **22/tcp et ICMP uniquement**, et seulement depuis `ssh_allowed_cidrs` |
|
||||
| Pare-feu | nftables, politique `drop` en entrée et en transit, avec `/etc/nftables.d/` pour les ajouts d'Ansible |
|
||||
| Mises à jour | `unattended-upgrades` activé |
|
||||
| Agent | `qemu-guest-agent` |
|
||||
|
||||
Le compte `ubuntu` livré par l'image n'est jamais créé, et son fichier sudoers est
|
||||
retiré.
|
||||
|
||||
## Une VM par fichier
|
||||
|
||||
Un fichier `vms/<nom>.tfvars` et un espace de travail Terraform par machine, chacune
|
||||
avec son propre état. Tout est décrit dans **[VMS.md](VMS.md)**.
|
||||
|
||||
Attention aux collisions avec le dépôt Debian, qui gère déjà la VM 120 en `10.0.5.20` :
|
||||
`vm_id`, `vm_hostname` et `vm_ipv4_address` doivent être uniques sur l'infrastructure,
|
||||
et rien ne le vérifie automatiquement.
|
||||
|
||||
## Prérequis
|
||||
|
||||
Identiques à ceux du dépôt Debian, et déjà en place sur votre nœud :
|
||||
|
||||
1. jeton d'API Proxmox avec `VM.*`, `Datastore.*`, `Sys.Audit` et `SDN.Use` ;
|
||||
2. accès SSH au nœud, le dépôt du snippet cloud-init ne passant pas par l'API ;
|
||||
3. contenu `snippets` **et** `import` activés sur le stockage `local` ;
|
||||
4. adresse IP exclue de la plage DHCP d'OPNsense.
|
||||
|
||||
La CI et les secrets sont décrits dans **[CICD.md](CICD.md)**.
|
||||
|
||||
## Après l'apply
|
||||
|
||||
`terraform apply` produit la VM démarrée, qui exécute cloud-init pendant **3 à 6
|
||||
minutes**, et `generated/<nom>-inventory.yml` pour Ansible.
|
||||
|
||||
```bash
|
||||
ssh [email protected] "cloud-init status --wait; command -v sudo || echo 'sudo absent'; sudo -n true 2>&1 | head -1"
|
||||
```
|
||||
|
||||
`sudo` existe toujours en tant que fichier, mais son exécution est refusée à tout le
|
||||
monde. C'est la seconde commande qui le vérifie.
|
||||
|
||||
## Points d'attention
|
||||
|
||||
- **cloud-init ne rejoue rien.** Modifier le template après création ne change pas une
|
||||
VM existante. Pour repartir propre : `destroy` puis `apply`.
|
||||
- **Extension de l'image.** Canonical publie son image cloud en `.img` alors qu'il
|
||||
s'agit d'un qcow2. Le contenu `import` de Proxmox n'accepte pas cette extension : le
|
||||
fichier est donc renommé en `.qcow2` au téléchargement, sans conversion.
|
||||
- **Clés d'hôte.** Chaque recréation en change : prévoir `ssh-keygen -R <ip>`.
|
||||
@@ -0,0 +1,63 @@
|
||||
# Créer plusieurs VM avec ce dépôt
|
||||
|
||||
Une VM = un fichier `vms/<nom>.tfvars` + un espace de travail Terraform du même nom.
|
||||
Chaque VM a donc **son propre état** : en détruire une ne touche pas aux autres.
|
||||
|
||||
```
|
||||
vms/
|
||||
└── ubuntu-01.tfvars VM 122, Ubuntu 24.04, 10.0.5.22
|
||||
```
|
||||
|
||||
Dans le bucket S3, les états se rangent sous
|
||||
`env:/<nom>/backend/terraform-proxmox-serveur-ubuntu-24-04/…`, automatiquement.
|
||||
|
||||
## Ajouter une VM
|
||||
|
||||
Copier `vms/ubuntu-01.tfvars`, le renommer, puis adapter. Trois valeurs doivent être
|
||||
uniques **sur l'ensemble de l'infrastructure**, y compris vis-à-vis du dépôt Debian, et
|
||||
rien ne le vérifie :
|
||||
|
||||
- `vm_id`
|
||||
- `vm_hostname`
|
||||
- `vm_ipv4_address`
|
||||
|
||||
Répartition actuelle : le dépôt Debian occupe 120 / `10.0.5.20` et 121 / `10.0.5.21`,
|
||||
celui-ci démarre à 122 / `10.0.5.22`.
|
||||
|
||||
Un `git push` déclenche le plan de **toutes** les VM déclarées : c'est le filet qui
|
||||
attrape une erreur de syntaxe ou un fichier de clé manquant avant tout déploiement.
|
||||
|
||||
Pour créer la VM, lancer `deploy` en saisissant son nom dans le champ `vm`. Le workflow
|
||||
refuse un nom sans fichier correspondant et liste les VM connues.
|
||||
|
||||
## Changer de version d'Ubuntu
|
||||
|
||||
Deux lignes, dans le fichier de la VM concernée :
|
||||
|
||||
```hcl
|
||||
ubuntu_image_url = "https://cloud-images.ubuntu.com/releases/26.04/release/ubuntu-26.04-server-cloudimg-amd64.img"
|
||||
ubuntu_image_file_name = "ubuntu-26.04-server-cloudimg-amd64.qcow2"
|
||||
```
|
||||
|
||||
Le nom de fichier doit garder une extension `.qcow2`, `.raw` ou `.vmdk` : Canonical
|
||||
publie en `.img`, que le contenu `import` de Proxmox refuse, alors que le format réel
|
||||
est bien un qcow2. Le renommage suffit, il n'y a pas de conversion.
|
||||
|
||||
Les versions LTS disponibles au moment de l'écriture : **24.04** (Noble Numbat) et
|
||||
**26.04** (Resolute Raccoon). Remplacer `24.04` par la version voulue dans les deux
|
||||
lignes, le numéro apparaissant à la fois dans le chemin et dans le nom du fichier.
|
||||
|
||||
## Commandes hors CI
|
||||
|
||||
```bash
|
||||
docker run --rm -it -v "$PWD":/work -w /work \
|
||||
-e TF_VAR_pve_api_token -e TF_VAR_pve_ssh_password \
|
||||
-e AWS_ACCESS_KEY_ID -e AWS_SECRET_ACCESS_KEY \
|
||||
hashicorp/terraform:latest workspace list
|
||||
```
|
||||
|
||||
Chaque commande doit ensuite recevoir `-var-file=vms/<nom>.tfvars`, avec l'espace de
|
||||
travail correspondant sélectionné. Se tromper de couple espace/fichier est la seule
|
||||
vraie façon de se faire mal : Terraform comparerait l'état d'une VM à la définition
|
||||
d'une autre et proposerait de tout recréer. C'est pour cela que les workflows dérivent
|
||||
toujours les deux du même nom.
|
||||
@@ -0,0 +1,79 @@
|
||||
locals {
|
||||
fqdn = "${var.vm_hostname}.${var.dns_domain}"
|
||||
|
||||
# Les clés sont lues dans les fichiers du répertoire key/. Un fichier
|
||||
# référencé mais absent fait échouer le plan, ce qui est le comportement
|
||||
# voulu : mieux vaut un plan rouge qu'une VM inaccessible.
|
||||
# Un compte sans aucun fichier de clé n'est pas créé.
|
||||
comptes = [
|
||||
for compte in var.local_accounts : {
|
||||
name = compte.name
|
||||
gecos = compte.gecos
|
||||
ssh_keys = [for fichier in compte.ssh_key_files : trimspace(file("${path.module}/${fichier}"))]
|
||||
}
|
||||
if length(compte.ssh_key_files) > 0
|
||||
]
|
||||
|
||||
cles_root = [for fichier in var.root_ssh_key_files : trimspace(file("${path.module}/${fichier}"))]
|
||||
|
||||
toutes_les_cles = concat(local.cles_root, flatten([for compte in local.comptes : compte.ssh_keys]))
|
||||
|
||||
# root d'abord, puis les comptes réellement créés : c'est la liste exhaustive
|
||||
# des comptes autorisés à ouvrir une session SSH.
|
||||
allow_users = join(" ", concat(["root"], [for compte in local.comptes : compte.name]))
|
||||
}
|
||||
|
||||
# Mot de passe root : sert à « su - » et à la console Proxmox, jamais en SSH.
|
||||
# Il est généré une fois puis conservé dans l'état Terraform, à récupérer par
|
||||
# le workflow de déploiement pour être déposé dans Vaultwarden.
|
||||
resource "random_password" "root" {
|
||||
length = var.root_password_length
|
||||
special = true
|
||||
min_upper = 2
|
||||
min_lower = 2
|
||||
min_numeric = 2
|
||||
min_special = 2
|
||||
override_special = "!@#%^&*()-_=+[]{}:,.?"
|
||||
}
|
||||
|
||||
# Le fichier est déposé dans le contenu "snippets" du stockage, via SSH.
|
||||
# Attention : cloud-init ne rejoue la configuration qu'au premier démarrage.
|
||||
# Modifier ce snippet après coup n'a donc aucun effet sur une VM déjà installée.
|
||||
resource "proxmox_virtual_environment_file" "user_data" {
|
||||
content_type = "snippets"
|
||||
datastore_id = var.snippet_datastore_id
|
||||
node_name = var.pve_node
|
||||
|
||||
source_raw {
|
||||
file_name = "${var.vm_hostname}-user-data.yaml"
|
||||
|
||||
data = templatefile("${path.module}/templates/user-data.yaml.tftpl", {
|
||||
hostname = var.vm_hostname
|
||||
fqdn = local.fqdn
|
||||
timezone = var.timezone
|
||||
locale = var.locale
|
||||
comptes = local.comptes
|
||||
cles_root = local.cles_root
|
||||
allow_users = local.allow_users
|
||||
admin_cidrs = var.ssh_allowed_cidrs
|
||||
root_password_hash = random_password.root.bcrypt_hash
|
||||
})
|
||||
}
|
||||
|
||||
lifecycle {
|
||||
# Garde-fou : une valeur qui n'est pas une clé publique produirait une VM
|
||||
# sur laquelle personne ne pourrait se connecter, et cloud-init ne rejoue rien.
|
||||
precondition {
|
||||
condition = alltrue([
|
||||
for cle in local.toutes_les_cles :
|
||||
can(regex("^(ssh-ed25519|ssh-rsa|ecdsa-sha2-nistp[0-9]+|sk-ssh-ed25519) AAAA", cle))
|
||||
])
|
||||
error_message = "Un des fichiers de key/ ne contient pas une clé publique valide."
|
||||
}
|
||||
|
||||
precondition {
|
||||
condition = length(local.comptes) > 0
|
||||
error_message = "Aucun compte n'a de clé : la machine serait inaccessible."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
# Clés publiques
|
||||
|
||||
Un fichier `.pub` par identité, repris par les fichiers `vms/*.tfvars`. Seules des clés
|
||||
**publiques** ont leur place ici : elles sont versionnées, ce qui est leur usage normal.
|
||||
|
||||
| Fichier | Utilisé par |
|
||||
|---|---|
|
||||
| `hcornet.pub` | compte `hcornet` et accès root |
|
||||
| `ansible.pub` | compte `ansible` et accès root de Semaphore |
|
||||
| `invite.pub` | compte `invite`, à déposer pour activer le compte |
|
||||
|
||||
Ce sont les mêmes clés que dans le dépôt Debian : recopier les fichiers depuis
|
||||
`terraform-proxmox-serveur-debian-12/key/` évite d'en gérer un second jeu.
|
||||
|
||||
Un compte dont la liste `ssh_key_files` est vide n'est pas créé du tout.
|
||||
|
||||
Un fichier référencé mais absent fait échouer le plan, et une valeur qui n'est pas une
|
||||
clé publique est refusée par une précondition — avant toute création de VM, puisque
|
||||
cloud-init ne rejoue rien.
|
||||
@@ -0,0 +1 @@
|
||||
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFDIXkfS5dM3po9Jz7NKgd3t1H4vqh0/cVoxrAP42ivd thedj@ZEU
|
||||
@@ -0,0 +1,124 @@
|
||||
resource "proxmox_virtual_environment_download_file" "ubuntu_cloud_image" {
|
||||
content_type = "import"
|
||||
datastore_id = var.image_datastore_id
|
||||
node_name = var.pve_node
|
||||
url = var.ubuntu_image_url
|
||||
file_name = var.ubuntu_image_file_name
|
||||
checksum = var.ubuntu_image_checksum
|
||||
checksum_algorithm = var.ubuntu_image_checksum == null ? null : var.ubuntu_image_checksum_algorithm
|
||||
|
||||
# L'image n'est pas retéléchargée à chaque apply, mais un fichier déjà
|
||||
# présent et non géré par Terraform est repris plutôt que de faire échouer le plan.
|
||||
overwrite = false
|
||||
overwrite_unmanaged = true
|
||||
upload_timeout = 1800
|
||||
}
|
||||
|
||||
resource "proxmox_virtual_environment_vm" "serveur" {
|
||||
name = var.vm_hostname
|
||||
description = "Serveur Ubuntu - gere par Terraform, ne pas modifier a la main"
|
||||
tags = ["terraform", "ubuntu2404"]
|
||||
|
||||
node_name = var.pve_node
|
||||
vm_id = var.vm_id
|
||||
|
||||
on_boot = true
|
||||
started = true
|
||||
stop_on_destroy = true
|
||||
|
||||
# i440fx et non q35 : sur q35, Proxmox presente le lecteur cloud-init (ide2)
|
||||
# via AHCI, que le noyau de l'image genericcloud ne detecte pas. ds-identify
|
||||
# ne trouve alors aucune source NoCloud et desactive cloud-init en silence.
|
||||
machine = "pc"
|
||||
bios = "seabios"
|
||||
scsi_hardware = "virtio-scsi-single"
|
||||
boot_order = ["scsi0"]
|
||||
|
||||
operating_system {
|
||||
type = "l26"
|
||||
}
|
||||
|
||||
agent {
|
||||
enabled = true
|
||||
trim = true
|
||||
}
|
||||
|
||||
cpu {
|
||||
cores = var.vm_cores
|
||||
type = "host"
|
||||
}
|
||||
|
||||
memory {
|
||||
dedicated = var.vm_memory
|
||||
}
|
||||
|
||||
disk {
|
||||
datastore_id = var.vm_datastore_id
|
||||
import_from = proxmox_virtual_environment_download_file.ubuntu_cloud_image.id
|
||||
interface = "scsi0"
|
||||
size = var.vm_disk_size
|
||||
file_format = "qcow2"
|
||||
discard = "on"
|
||||
iothread = true
|
||||
ssd = true
|
||||
}
|
||||
|
||||
network_device {
|
||||
bridge = var.vm_bridge
|
||||
model = "virtio"
|
||||
}
|
||||
|
||||
# Console série : permet un « qm terminal 120 » depuis le nœud si le réseau
|
||||
# de la VM est cassé, sans ouvrir de service supplémentaire.
|
||||
serial_device {}
|
||||
|
||||
initialization {
|
||||
datastore_id = var.vm_datastore_id
|
||||
interface = "ide2"
|
||||
|
||||
dns {
|
||||
domain = var.dns_domain
|
||||
servers = var.dns_servers
|
||||
}
|
||||
|
||||
ip_config {
|
||||
ipv4 {
|
||||
address = "${var.vm_ipv4_address}/${var.vm_ipv4_prefix}"
|
||||
gateway = var.vm_ipv4_gateway
|
||||
}
|
||||
}
|
||||
|
||||
user_data_file_id = proxmox_virtual_environment_file.user_data.id
|
||||
}
|
||||
|
||||
lifecycle {
|
||||
# Proxmox renvoie un type de machine versionné (pc-i440fx-11.0+pve2) et
|
||||
# réordonne le boot : sans cela, chaque plan proposerait une modification.
|
||||
ignore_changes = [
|
||||
machine,
|
||||
boot_order,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
resource "local_file" "ansible_inventory" {
|
||||
filename = coalesce(var.ansible_inventory_path, "generated/${var.vm_hostname}-inventory.yml")
|
||||
file_permission = "0644"
|
||||
|
||||
content = yamlencode({
|
||||
all = {
|
||||
children = {
|
||||
(var.ansible_group) = {
|
||||
hosts = {
|
||||
(var.vm_hostname) = {
|
||||
ansible_host = var.vm_ipv4_address
|
||||
ansible_user = var.ansible_remote_user
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
depends_on = [proxmox_virtual_environment_vm.serveur]
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
output "vm_id" {
|
||||
description = "VMID de la machine créée."
|
||||
value = proxmox_virtual_environment_vm.serveur.vm_id
|
||||
}
|
||||
|
||||
output "vm_ipv4_address" {
|
||||
description = "Adresse IPv4 fixe de la machine."
|
||||
value = var.vm_ipv4_address
|
||||
}
|
||||
|
||||
output "vm_fqdn" {
|
||||
description = "Nom pleinement qualifié de la machine."
|
||||
value = local.fqdn
|
||||
}
|
||||
|
||||
output "comptes" {
|
||||
description = "Comptes ouverts sur la machine, hors root."
|
||||
value = [for compte in local.comptes : compte.name]
|
||||
}
|
||||
|
||||
output "ssh_command" {
|
||||
description = "Commande de connexion une fois cloud-init terminé."
|
||||
value = "ssh ${local.comptes[0].name}@${var.vm_ipv4_address}"
|
||||
}
|
||||
|
||||
output "root_password" {
|
||||
description = "Mot de passe root, à déposer dans Vaultwarden puis à ne plus consulter."
|
||||
value = random_password.root.result
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
output "ansible_inventory_file" {
|
||||
description = "Inventaire généré, à recopier dans le dépôt Ansible."
|
||||
value = local_file.ansible_inventory.filename
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
provider "proxmox" {
|
||||
endpoint = var.pve_endpoint
|
||||
api_token = var.pve_api_token
|
||||
insecure = var.pve_insecure
|
||||
|
||||
# Le dépôt d'un snippet cloud-init passe obligatoirement par SSH :
|
||||
# l'API Proxmox ne permet pas de téléverser dans le contenu "snippets".
|
||||
ssh {
|
||||
agent = false
|
||||
username = var.pve_ssh_username
|
||||
password = var.pve_ssh_password
|
||||
|
||||
node {
|
||||
name = var.pve_node
|
||||
address = var.pve_node_address
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Lit les secrets nécessaires au déploiement dans OpenBao et les pousse dans
|
||||
# l'environnement du job Gitea. Aucune valeur n'est affichée : chaque secret est
|
||||
# masqué dans le journal avant d'être exporté.
|
||||
#
|
||||
# Attendus en entrée (variables d'environnement) :
|
||||
# OPENBAO_ADDR variable de dépôt Gitea, ex. https://openbao.tips-of-mine.com
|
||||
# OPENBAO_ROLE_ID secret Gitea
|
||||
# OPENBAO_SECRET_ID secret Gitea
|
||||
#
|
||||
set -euo pipefail
|
||||
|
||||
# Adresse d'OpenBao : valeur par défaut du homelab, surchargeable par
|
||||
# une variable d'environnement si besoin.
|
||||
OPENBAO_ADDR="${OPENBAO_ADDR:-https://openbao.tips-of-mine.com}"
|
||||
: "${OPENBAO_ROLE_ID:?variable OPENBAO_ROLE_ID absente}"
|
||||
: "${OPENBAO_SECRET_ID:?variable OPENBAO_SECRET_ID absente}"
|
||||
: "${GITHUB_ENV:?ce script est prévu pour un job Gitea Actions}"
|
||||
|
||||
masquer() { printf '::add-mask::%s\n' "$1"; }
|
||||
exporter() { printf '%s=%s\n' "$1" "$2" >>"${GITHUB_ENV}"; }
|
||||
|
||||
exiger() {
|
||||
# $1 = valeur, $2 = description
|
||||
if [ -z "$1" ] || [ "$1" = "null" ]; then
|
||||
echo "Secret manquant dans OpenBao : $2" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# --- Authentification AppRole -------------------------------------------------
|
||||
BAO_TOKEN="$(
|
||||
curl -sS --fail-with-body \
|
||||
--request POST \
|
||||
--data "{\"role_id\":\"${OPENBAO_ROLE_ID}\",\"secret_id\":\"${OPENBAO_SECRET_ID}\"}" \
|
||||
"${OPENBAO_ADDR}/v1/auth/approle/login" | jq -r '.auth.client_token // empty'
|
||||
)"
|
||||
exiger "${BAO_TOKEN}" "jeton AppRole (vérifier role_id / secret_id)"
|
||||
masquer "${BAO_TOKEN}"
|
||||
|
||||
lire_secret() {
|
||||
curl -sS --fail-with-body \
|
||||
-H "X-Vault-Token: ${BAO_TOKEN}" \
|
||||
"${OPENBAO_ADDR}/v1/secret/data/$1" | jq -r '.data.data'
|
||||
}
|
||||
|
||||
# --- Accès Proxmox ------------------------------------------------------------
|
||||
PROXMOX="$(lire_secret proxmox)"
|
||||
API_URL="$(jq -r '.api_url // empty' <<<"${PROXMOX}")"
|
||||
API_TOKEN="$(jq -r '.api_token // empty' <<<"${PROXMOX}")"
|
||||
NODE_NAME="$(jq -r '.node_name // empty' <<<"${PROXMOX}")"
|
||||
SSH_PASSWORD="$(jq -r '.ssh_password // empty' <<<"${PROXMOX}")"
|
||||
|
||||
exiger "${API_URL}" "secret/proxmox → api_url"
|
||||
exiger "${API_TOKEN}" "secret/proxmox → api_token"
|
||||
exiger "${NODE_NAME}" "secret/proxmox → node_name"
|
||||
exiger "${SSH_PASSWORD}" "secret/proxmox → ssh_password (à ajouter, le dépôt du snippet cloud-init passe par SSH)"
|
||||
|
||||
# Adresse du nœud déduite de l'URL de l'API, pour ne pas dupliquer l'information.
|
||||
NODE_ADDRESS="$(sed -E 's#^https?://([^:/]+).*#\1#' <<<"${API_URL}")"
|
||||
exiger "${NODE_ADDRESS}" "adresse du nœud déduite de api_url"
|
||||
|
||||
masquer "${API_TOKEN}"
|
||||
masquer "${SSH_PASSWORD}"
|
||||
|
||||
exporter TF_VAR_pve_endpoint "${API_URL}"
|
||||
exporter TF_VAR_pve_api_token "${API_TOKEN}"
|
||||
exporter TF_VAR_pve_node "${NODE_NAME}"
|
||||
exporter TF_VAR_pve_node_address "${NODE_ADDRESS}"
|
||||
exporter TF_VAR_pve_ssh_password "${SSH_PASSWORD}"
|
||||
|
||||
# --- Backend S3 ---------------------------------------------------------------
|
||||
AWS="$(lire_secret aws)"
|
||||
AWS_KEY="$(jq -r '.AWS_ACCESS_KEY_ID // empty' <<<"${AWS}")"
|
||||
AWS_SECRET="$(jq -r '.AWS_SECRET_ACCESS_KEY // empty' <<<"${AWS}")"
|
||||
|
||||
exiger "${AWS_KEY}" "secret/aws → AWS_ACCESS_KEY_ID"
|
||||
exiger "${AWS_SECRET}" "secret/aws → AWS_SECRET_ACCESS_KEY"
|
||||
|
||||
masquer "${AWS_KEY}"
|
||||
masquer "${AWS_SECRET}"
|
||||
|
||||
exporter AWS_ACCESS_KEY_ID "${AWS_KEY}"
|
||||
exporter AWS_SECRET_ACCESS_KEY "${AWS_SECRET}"
|
||||
|
||||
echo "Secrets chargés depuis OpenBao : Proxmox (4 valeurs), backend S3 (2 valeurs)."
|
||||
@@ -0,0 +1,13 @@
|
||||
terraform {
|
||||
# Convention du homelab : un bucket unique, une cle par projet.
|
||||
# Les identifiants AWS ne sont pas ici : la CI les exporte en variables
|
||||
# d'environnement apres les avoir lus dans OpenBao (secret/aws).
|
||||
backend "s3" {
|
||||
bucket = "tips-of-mine-terraform-bucket-backend"
|
||||
key = "backend/terraform-proxmox-serveur-ubuntu-24-04/terraform.tfstate"
|
||||
region = "eu-north-1"
|
||||
|
||||
# Pas de verrouillage, comme sur les autres projets : aucune table DynamoDB
|
||||
# en place. Le garde-fou est le "concurrency" des workflows.
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
#cloud-config
|
||||
# Généré par Terraform — toute modification manuelle sera écrasée.
|
||||
# Rappel : ce fichier n'est appliqué qu'au tout premier démarrage de la VM.
|
||||
|
||||
hostname: ${hostname}
|
||||
fqdn: ${fqdn}
|
||||
prefer_fqdn_over_hostname: true
|
||||
manage_etc_hosts: true
|
||||
timezone: ${timezone}
|
||||
|
||||
# La clé "users" remplace la liste par défaut : le compte "ubuntu" livré avec
|
||||
# l'image cloud n'est donc jamais créé, ni son fichier sudoers.
|
||||
#
|
||||
# Aucun compte n'appartient au groupe sudo, et la commande sudo est neutralisée
|
||||
# plus bas. Pour administrer : ouvrir une session avec son compte, puis
|
||||
# « su - » avec le mot de passe root.
|
||||
users:
|
||||
%{ for compte in comptes ~}
|
||||
- name: ${compte.name}
|
||||
gecos: "${compte.gecos}"
|
||||
shell: /bin/bash
|
||||
lock_passwd: true
|
||||
ssh_authorized_keys:
|
||||
%{ for cle in compte.ssh_keys ~}
|
||||
- "${cle}"
|
||||
%{ endfor ~}
|
||||
%{ endfor ~}
|
||||
|
||||
# Root garde un mot de passe, utilisable par « su - » et sur la console Proxmox.
|
||||
# En SSH, seule la clé est acceptée (prohibit-password plus bas).
|
||||
disable_root: false
|
||||
ssh_pwauth: false
|
||||
ssh_deletekeys: true
|
||||
ssh_genkeytypes: [ed25519, rsa]
|
||||
|
||||
chpasswd:
|
||||
expire: false
|
||||
users:
|
||||
- name: root
|
||||
password: "${root_password_hash}"
|
||||
type: hash
|
||||
|
||||
package_update: true
|
||||
package_upgrade: true
|
||||
|
||||
# Strict minimum : agent invité, pare-feu, mises à jour de sécurité,
|
||||
# et les prérequis d'Ansible et de l'installateur Centreon.
|
||||
packages:
|
||||
- qemu-guest-agent
|
||||
- nftables
|
||||
- unattended-upgrades
|
||||
- apt-listchanges
|
||||
- locales
|
||||
- python3-apt
|
||||
- ca-certificates
|
||||
- curl
|
||||
- wget
|
||||
- gnupg
|
||||
- lsb-release
|
||||
- apt-transport-https
|
||||
|
||||
write_files:
|
||||
- path: /root/.ssh/authorized_keys
|
||||
owner: root:root
|
||||
permissions: "0600"
|
||||
content: |
|
||||
%{ for cle in cles_root ~}
|
||||
${cle}
|
||||
%{ endfor ~}
|
||||
|
||||
- path: /etc/ssh/sshd_config.d/99-durcissement.conf
|
||||
owner: root:root
|
||||
permissions: "0600"
|
||||
content: |
|
||||
# Durcissement SSH — cloud-init
|
||||
# root n'est joignable que par clé : le mot de passe root ne sert qu'en
|
||||
# local, par « su - » ou sur la console.
|
||||
PermitRootLogin prohibit-password
|
||||
PasswordAuthentication no
|
||||
KbdInteractiveAuthentication no
|
||||
PermitEmptyPasswords no
|
||||
PubkeyAuthentication yes
|
||||
AuthenticationMethods publickey
|
||||
AllowUsers ${allow_users}
|
||||
MaxAuthTries 3
|
||||
LoginGraceTime 30
|
||||
X11Forwarding no
|
||||
AllowAgentForwarding no
|
||||
AllowTcpForwarding no
|
||||
ClientAliveInterval 300
|
||||
ClientAliveCountMax 2
|
||||
|
||||
- path: /etc/nftables.conf
|
||||
owner: root:root
|
||||
permissions: "0640"
|
||||
content: |
|
||||
#!/usr/sbin/nft -f
|
||||
# Jeu de règles principal — cloud-init.
|
||||
# En entrée, seuls SSH et ICMP sont ouverts. Les ports applicatifs
|
||||
# (web, SNMP, gorgone) sont ajoutés par Ansible dans /etc/nftables.d/.
|
||||
flush ruleset
|
||||
|
||||
table inet filter {
|
||||
set reseaux_admin {
|
||||
type ipv4_addr
|
||||
flags interval
|
||||
elements = { ${join(", ", admin_cidrs)} }
|
||||
}
|
||||
|
||||
chain input {
|
||||
type filter hook input priority filter; policy drop;
|
||||
|
||||
iif "lo" accept
|
||||
ct state established,related accept
|
||||
ct state invalid drop
|
||||
|
||||
ip protocol icmp icmp type { echo-request, destination-unreachable, time-exceeded, parameter-problem } accept
|
||||
ip6 nexthdr ipv6-icmp accept
|
||||
|
||||
tcp dport 22 ip saddr @reseaux_admin ct state new accept
|
||||
|
||||
include "/etc/nftables.d/*.nft"
|
||||
|
||||
counter comment "paquets rejetes en entree"
|
||||
}
|
||||
|
||||
chain forward {
|
||||
type filter hook forward priority filter; policy drop;
|
||||
}
|
||||
|
||||
chain output {
|
||||
type filter hook output priority filter; policy accept;
|
||||
}
|
||||
}
|
||||
|
||||
- path: /etc/nftables.d/00-placeholder.nft
|
||||
owner: root:root
|
||||
permissions: "0640"
|
||||
content: |
|
||||
# Fragments de règles ajoutés par Ansible. Ne rien mettre ici.
|
||||
|
||||
- path: /etc/apt/apt.conf.d/20auto-upgrades
|
||||
owner: root:root
|
||||
permissions: "0644"
|
||||
content: |
|
||||
APT::Periodic::Update-Package-Lists "1";
|
||||
APT::Periodic::Unattended-Upgrade "1";
|
||||
|
||||
- path: /etc/sysctl.d/99-durcissement.conf
|
||||
owner: root:root
|
||||
permissions: "0644"
|
||||
content: |
|
||||
net.ipv4.conf.all.accept_redirects = 0
|
||||
net.ipv4.conf.default.accept_redirects = 0
|
||||
net.ipv4.conf.all.send_redirects = 0
|
||||
net.ipv4.conf.all.accept_source_route = 0
|
||||
net.ipv6.conf.all.accept_redirects = 0
|
||||
net.ipv4.icmp_echo_ignore_broadcasts = 1
|
||||
net.ipv4.tcp_syncookies = 1
|
||||
kernel.dmesg_restrict = 1
|
||||
kernel.kptr_restrict = 2
|
||||
fs.protected_hardlinks = 1
|
||||
fs.protected_symlinks = 1
|
||||
|
||||
runcmd:
|
||||
- chmod 700 /root/.ssh
|
||||
- sed -i "s|^# *${locale} UTF-8|${locale} UTF-8|" /etc/locale.gen
|
||||
- locale-gen
|
||||
- update-locale LANG=${locale}
|
||||
- sysctl --system > /dev/null
|
||||
- systemctl enable --now qemu-guest-agent
|
||||
- systemctl enable --now nftables
|
||||
- systemctl enable --now unattended-upgrades
|
||||
- systemctl restart ssh.socket || systemctl restart ssh
|
||||
# sudo n'est pas purgé : il est une dépendance d'ubuntu-minimal, et le
|
||||
# supprimer entraînerait le métapaquet puis, à l'autoremove, netplan.io, udev
|
||||
# ou vim-tiny. On lui retire donc setuid et tous ses droits : la commande
|
||||
# devient inutilisable par quiconque, le paquet reste en place et la
|
||||
# dépendance est satisfaite. dpkg-statoverride survit aux mises à jour.
|
||||
- dpkg-statoverride --update --add root root 0000 /usr/bin/sudo
|
||||
- rm -f /etc/sudoers.d/90-cloud-init-users
|
||||
- apt-get -y autoremove --purge
|
||||
- apt-get -y clean
|
||||
|
||||
final_message: "cloud-init termine apres $UPTIME s - ${fqdn} est pret pour Ansible"
|
||||
+282
@@ -0,0 +1,282 @@
|
||||
############################################
|
||||
# Accès au nœud Proxmox
|
||||
############################################
|
||||
|
||||
variable "pve_endpoint" {
|
||||
description = "URL de l'API Proxmox (ex. https://10.0.4.5:8006/)."
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "pve_api_token" {
|
||||
description = "Jeton d'API au format user@realm!tokenid=uuid. À fournir par TF_VAR_pve_api_token."
|
||||
type = string
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
variable "pve_insecure" {
|
||||
description = "Ne pas vérifier le certificat TLS de l'API (nécessaire si l'endpoint est appelé par IP)."
|
||||
type = bool
|
||||
default = true
|
||||
}
|
||||
|
||||
variable "pve_node" {
|
||||
description = "Nom du nœud Proxmox cible."
|
||||
type = string
|
||||
default = "pve"
|
||||
}
|
||||
|
||||
variable "pve_node_address" {
|
||||
description = "Adresse IP du nœud, utilisée pour la connexion SSH de dépôt du snippet."
|
||||
type = string
|
||||
default = "10.0.4.5"
|
||||
}
|
||||
|
||||
variable "pve_ssh_username" {
|
||||
description = "Compte SSH sur le nœud Proxmox (doit pouvoir écrire dans le répertoire des snippets)."
|
||||
type = string
|
||||
default = "root"
|
||||
}
|
||||
|
||||
variable "pve_ssh_password" {
|
||||
description = "Mot de passe SSH du nœud. À fournir par TF_VAR_pve_ssh_password."
|
||||
type = string
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
############################################
|
||||
# Image Ubuntu
|
||||
############################################
|
||||
|
||||
variable "ubuntu_image_url" {
|
||||
description = "URL de l'image cloud Ubuntu Server. L'image est un qcow2, malgré son extension .img."
|
||||
type = string
|
||||
default = "https://cloud-images.ubuntu.com/releases/24.04/release/ubuntu-24.04-server-cloudimg-amd64.img"
|
||||
}
|
||||
|
||||
variable "ubuntu_image_file_name" {
|
||||
description = <<-EOT
|
||||
Nom du fichier stocké sur le nœud. Le contenu import de Proxmox n'accepte que
|
||||
les extensions .qcow2, .raw et .vmdk : l'image Ubuntu, publiée en .img, est
|
||||
donc renommée ici. Son format réel ne change pas, c'est bien un qcow2.
|
||||
EOT
|
||||
|
||||
type = string
|
||||
default = "ubuntu-24.04-server-cloudimg-amd64.qcow2"
|
||||
}
|
||||
|
||||
variable "ubuntu_image_checksum" {
|
||||
description = "Empreinte de l'image (facultatif). Laisser à null pour ne pas vérifier."
|
||||
type = string
|
||||
default = null
|
||||
sensitive = false
|
||||
}
|
||||
|
||||
variable "ubuntu_image_checksum_algorithm" {
|
||||
description = "Algorithme de l'empreinte fournie (sha256, sha512...)."
|
||||
type = string
|
||||
default = "sha512"
|
||||
}
|
||||
|
||||
variable "image_datastore_id" {
|
||||
description = "Stockage recevant l'image téléchargée (doit accepter le contenu iso)."
|
||||
type = string
|
||||
default = "local"
|
||||
}
|
||||
|
||||
variable "snippet_datastore_id" {
|
||||
description = "Stockage recevant le snippet cloud-init (doit accepter le contenu snippets)."
|
||||
type = string
|
||||
default = "local"
|
||||
}
|
||||
|
||||
############################################
|
||||
# Définition de la VM
|
||||
############################################
|
||||
|
||||
variable "vm_id" {
|
||||
description = "VMID de la machine."
|
||||
type = number
|
||||
default = 120
|
||||
}
|
||||
|
||||
variable "vm_hostname" {
|
||||
description = "Nom court de la machine (nom de VM et hostname système)."
|
||||
type = string
|
||||
default = "ubuntu-01"
|
||||
}
|
||||
|
||||
variable "vm_datastore_id" {
|
||||
description = "Stockage du disque système et du disque cloud-init."
|
||||
type = string
|
||||
default = "local"
|
||||
}
|
||||
|
||||
variable "vm_cores" {
|
||||
description = <<-EOT
|
||||
Nombre de cœurs. Les besoins applicatifs se vérifient côté Ansible, pas ici :
|
||||
ce dépôt crée des VM pour n'importe quel usage.
|
||||
EOT
|
||||
|
||||
type = number
|
||||
default = 2
|
||||
|
||||
validation {
|
||||
condition = var.vm_cores >= 1
|
||||
error_message = "Il faut au moins un cœur."
|
||||
}
|
||||
}
|
||||
|
||||
variable "vm_memory" {
|
||||
description = "Mémoire en Mio. Ubuntu démarre à partir de 512 Mio ; les besoins applicatifs se vérifient côté Ansible."
|
||||
type = number
|
||||
default = 4096
|
||||
|
||||
validation {
|
||||
condition = var.vm_memory >= 512
|
||||
error_message = "En dessous de 512 Mio, l'image cloud Ubuntu ne démarre pas correctement."
|
||||
}
|
||||
}
|
||||
|
||||
variable "vm_disk_size" {
|
||||
description = "Taille du disque système en Gio."
|
||||
type = number
|
||||
default = 40
|
||||
}
|
||||
|
||||
variable "vm_bridge" {
|
||||
description = "Bridge de rattachement de la carte réseau."
|
||||
type = string
|
||||
default = "vmbr3"
|
||||
}
|
||||
|
||||
############################################
|
||||
# Réseau
|
||||
############################################
|
||||
|
||||
variable "vm_ipv4_address" {
|
||||
description = "Adresse IPv4 fixe de la VM."
|
||||
type = string
|
||||
default = "10.0.5.20"
|
||||
}
|
||||
|
||||
variable "vm_ipv4_prefix" {
|
||||
description = "Longueur du préfixe IPv4."
|
||||
type = number
|
||||
default = 24
|
||||
}
|
||||
|
||||
variable "vm_ipv4_gateway" {
|
||||
description = "Passerelle IPv4 (interface LAN d'OPNsense)."
|
||||
type = string
|
||||
default = "10.0.5.1"
|
||||
}
|
||||
|
||||
variable "dns_servers" {
|
||||
description = "Serveurs DNS poussés par cloud-init."
|
||||
type = list(string)
|
||||
default = ["10.0.5.1"]
|
||||
}
|
||||
|
||||
variable "dns_domain" {
|
||||
description = "Domaine de recherche et suffixe du FQDN."
|
||||
type = string
|
||||
default = "tips-of-mine.local"
|
||||
}
|
||||
|
||||
############################################
|
||||
# Système et accès
|
||||
############################################
|
||||
|
||||
variable "timezone" {
|
||||
description = "Fuseau horaire du système, appliqué par cloud-init."
|
||||
type = string
|
||||
default = "Europe/Paris"
|
||||
}
|
||||
|
||||
variable "locale" {
|
||||
description = "Locale générée sur le système."
|
||||
type = string
|
||||
default = "fr_FR.UTF-8"
|
||||
}
|
||||
|
||||
variable "local_accounts" {
|
||||
description = <<-EOT
|
||||
Comptes unix créés par cloud-init. Aucun n'a de privilège : la commande sudo
|
||||
est retirée du système. Pour administrer, on ouvre une session avec son
|
||||
compte puis on bascule par « su - » avec le mot de passe root.
|
||||
|
||||
ssh_key_files référence des fichiers du répertoire key/, relatifs à la racine
|
||||
du dépôt. Un compte sans aucun fichier de clé n'est pas créé : c'est le moyen
|
||||
de déclarer un compte à l'avance sans l'activer.
|
||||
EOT
|
||||
|
||||
type = list(object({
|
||||
name = string
|
||||
gecos = optional(string, "")
|
||||
ssh_key_files = list(string)
|
||||
}))
|
||||
|
||||
validation {
|
||||
condition = length(var.local_accounts) > 0
|
||||
error_message = "Au moins un compte est nécessaire pour accéder à la machine."
|
||||
}
|
||||
|
||||
validation {
|
||||
condition = !contains([for c in var.local_accounts : c.name], "root")
|
||||
error_message = "Le compte root se configure par root_ssh_key_files, pas par local_accounts."
|
||||
}
|
||||
}
|
||||
|
||||
variable "root_ssh_key_files" {
|
||||
description = <<-EOT
|
||||
Fichiers de clés publiques autorisées à ouvrir une session SSH directement en
|
||||
root, sudo n'existant pas sur la machine. C'est par là qu'Ansible obtient ses
|
||||
droits, et c'est aussi la voie d'accès root d'un administrateur qui y dépose
|
||||
sa clé.
|
||||
EOT
|
||||
|
||||
type = list(string)
|
||||
|
||||
validation {
|
||||
condition = length(var.root_ssh_key_files) > 0
|
||||
error_message = "Sans clé root, aucune configuration ne serait possible sur la machine."
|
||||
}
|
||||
}
|
||||
|
||||
variable "ansible_group" {
|
||||
description = "Groupe d'inventaire Ansible dans lequel ranger la machine."
|
||||
type = string
|
||||
default = "serveurs"
|
||||
}
|
||||
|
||||
variable "ansible_remote_user" {
|
||||
description = "Compte utilisé par Ansible dans l'inventaire généré."
|
||||
type = string
|
||||
default = "root"
|
||||
}
|
||||
|
||||
variable "root_password_length" {
|
||||
description = "Longueur du mot de passe root généré. Il sert à « su - » et à la console Proxmox."
|
||||
type = number
|
||||
default = 24
|
||||
}
|
||||
|
||||
variable "ssh_allowed_cidrs" {
|
||||
description = "Réseaux autorisés à joindre le port 22 (pare-feu local nftables)."
|
||||
type = list(string)
|
||||
default = ["10.0.4.0/24", "10.0.5.0/24"]
|
||||
}
|
||||
|
||||
############################################
|
||||
# Sortie pour Ansible
|
||||
############################################
|
||||
|
||||
variable "ansible_inventory_path" {
|
||||
description = <<-EOT
|
||||
Chemin du fichier d'inventaire généré à la fin de l'apply. Laissé à null,
|
||||
il vaut generated/<nom de la VM>-inventory.yml : chaque VM a le sien.
|
||||
EOT
|
||||
|
||||
type = string
|
||||
default = null
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
terraform {
|
||||
required_version = ">= 1.6.0"
|
||||
|
||||
required_providers {
|
||||
proxmox = {
|
||||
source = "bpg/proxmox"
|
||||
version = ">= 0.69.0"
|
||||
}
|
||||
random = {
|
||||
source = "hashicorp/random"
|
||||
version = ">= 3.6.0"
|
||||
}
|
||||
local = {
|
||||
source = "hashicorp/local"
|
||||
version = ">= 2.4.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
# Valeurs non sensibles, versionnées : c'est ce fichier que la CI passe en -var-file.
|
||||
# Les secrets (jeton d'API, mot de passe SSH du nœud) arrivent par TF_VAR_*, depuis OpenBao.
|
||||
#
|
||||
# Trois valeurs doivent être uniques sur l'infrastructure, rien ne le vérifie
|
||||
# automatiquement : vm_id, vm_hostname et vm_ipv4_address. Attention, le dépôt
|
||||
# Debian gère déjà la VM 120 en 10.0.5.20.
|
||||
|
||||
vm_id = 122
|
||||
vm_hostname = "ubuntu-01"
|
||||
vm_bridge = "vmbr3"
|
||||
|
||||
vm_ipv4_address = "10.0.5.22"
|
||||
vm_ipv4_gateway = "10.0.5.1"
|
||||
dns_servers = ["10.0.5.1"]
|
||||
dns_domain = "tips-of-mine.local"
|
||||
|
||||
vm_cores = 2
|
||||
vm_memory = 2048
|
||||
vm_disk_size = 20
|
||||
|
||||
ansible_group = "serveurs"
|
||||
|
||||
ssh_allowed_cidrs = ["10.0.4.0/24", "10.0.5.0/24"]
|
||||
|
||||
# Comptes de la machine. Aucun n'a de privilège : sudo est neutralisé.
|
||||
# Pour administrer, ouvrir une session avec son compte puis « su - ».
|
||||
# Les clés vivent dans key/, un fichier .pub par identité.
|
||||
# Une liste ssh_key_files vide signifie : compte déclaré mais non créé.
|
||||
local_accounts = [
|
||||
{
|
||||
name = "hcornet"
|
||||
gecos = "Hubert Cornet - administration"
|
||||
ssh_key_files = ["key/hcornet.pub"]
|
||||
},
|
||||
{
|
||||
name = "invite"
|
||||
gecos = "Compte de consultation - aucun privilege"
|
||||
ssh_key_files = []
|
||||
},
|
||||
{
|
||||
name = "ansible"
|
||||
gecos = "Compte de service - sans privilege"
|
||||
ssh_key_files = ["key/ansible.pub"]
|
||||
},
|
||||
]
|
||||
|
||||
# Clés autorisées à ouvrir une session directement en root.
|
||||
# key/ansible.pub est celle que Semaphore présente : sans elle ici, le playbook
|
||||
# se voit refuser la connexion après chaque recréation de la VM.
|
||||
root_ssh_key_files = ["key/hcornet.pub", "key/ansible.pub"]
|
||||
Reference in new issue
Block a user