Ci-dessous nous prendrons en exemple le cas du provider OCI, mais les concepts sont les mêmes pour AWS, GCP, Azure, …
Notion de secrets
Dans Terraform un secret est une valeur qui ne doit pas être exposée car cette valeur est sensible.
Dans OCI toutes les ressources sont identifiées par un OCID, qui est un identifiant unique.
Les OCID sont des identifiants uniques mais ce ne sont pas des secrets au sens Terraform. Leur exposition n’a pas d’effet si l’attaquant n’a pas des autorisations d’accès IAM.
Par exemple connaître l’OCID d’une ressource compute ne permet pas de s’y connecter sans une clef ssh ou un accès IAM.
compartment_id et subnet_id sont nécessaires pour les références dans Terraform et sont stockés en clair dans le tfstate.
Ces 2 attributs ne peuvent donc pas être déclarés comme « éphémères », ils doivent être persistés dans le tfstate.
En revanche la clef privée oci_user_opc_private_key que l’on passe à notre terraform afin qu’il puisse se connecter en ssh à l’instance de base de données créée (pour exécuter quelques commandes d’initialisation telles que l’ajout d’un utilisateur ansible par exemple), cette clef est un secret ! En plus d’être récupérée d’un vault, cette ressource doit être définit comme « éphémère » et « sensible ».
sensitive ==> ne sera pas affiché dans la CLI
ephemeral ==> ne sera pas persisté dans le tfstate
variable "oci_user_opc_private_key" {
description = "Clé privée SSH temporairement utilisée pour se connecter avec l'utilisateur sudo opc"
type = string
sensitive = true
ephemeral = true
validation {
condition = length(var.oci_user_opc_private_key) > 10
error_message = "La taille de oci_user_opc_private_key est incorrecte."
}
}
Exemple de cas où l’on a un secret éphémère oci_user_opc_private_key
Lorsque vous managez vos serveurs avec ansible vous avez besoin d’installer un utilisateur sudo sur vos machine.
Dans OCI, la création d’une compute instance ==> utilisation de cloud init pour rajouter l’utilisateur sudo ansible, par exemple le fichier suivant
cloud_init_user_data.yml
#cloud-config
users:
- default # ← conserve l'utilisateur 'opc' et sa configuration par défaut
- name: myansibleuser
sudo: ALL=(ALL) NOPASSWD:ALL
shell: /bin/bash
ssh_authorized_keys:
- ${ssh_public_key_for_myansibleuser}
lock_passwd: true # Désactive la connexion par mot de passe
Dans OCI, la création d’une database instance ==> on ne peut pas utiliser cloud init !!! Car cloud init est déjà utilisé par OCI pour les actions d’amorçages de l’instance database puisqu’ils installent un serveur oracle et font un certains nombre d’actions avant de nous remettre cette instance.
Par conséquent une alternative possible est de se connecter via la clef privée de l’utilisateur sudo opc afin d’exécuter les actions souhaitées. Mais cette clef privée ne devra pas être stockée dans le tfstate et donc devra être éphémère (et bien sûr également « sensitive »). Elle pourra être déclarée éphémère car elle n’est utilisée qu’une seule fois par Terraform à la création de la ressource database instance afin de se connecter en ssh.
On utilisera donc une ressource pour se connecter en ssh à notre ressource db_system qui viendra d’être créée.
# Provisionnement post-création via SSH ==> car les db_system oracle ne supportent pas cloud-init
resource "null_resource" "bootstrap_dbnode" {
# depends_on garantit l'ordre d'exécution (donc après la création du db_system)
depends_on = [ time_sleep.wait_db_fully_created ]
# la ressource null_resource ne se déclenchera que si l'id de db change (==> création ou recréation)
triggers = {
db_id = oci_database_db_system.db_system.id
}
connection {
host = data.oci_core_vnic.db_node_vnic.public_ip_address
type = "ssh"
user = "opc"
#private_key = file("${path.module}/files/oci_user_opc_key") <=== pas secure !
private_key = var.oci_user_opc_private_key
}
provisioner "remote-exec" {
inline = [
# Nettoie le cache SSSD pour éviter les warnings (ces warnings ne nous concernent pas, c'est uniquement pour du LDAP/AD)
"sudo systemctl stop sssd || true",
"sudo rm -rf /var/lib/sss/db/* || true",
"sudo systemctl start sssd || true",
# On crée l'utilisateur myansibleuser, un utilisateur sudo qui sera utilisé par ansible pour exécuter des playbooks
"sudo useradd -m myansibleuser || true",
"echo 'myansibleuser ALL=(ALL) NOPASSWD:ALL' | sudo tee /etc/sudoers.d/myansibleuser",
"sudo mkdir -p /home/myansibleuser/.ssh",
"echo '${file("${path.module}/files/myansibleuser_key.pub")}' | sudo tee /home/myansibleuser/.ssh/authorized_keys",
"sudo chown -R myansibleuser:myansibleuser/home/myansibleuser/.ssh",
"sudo chmod 700 /home/myansibleuser/.ssh",
"sudo chmod 600 /home/myansibleuser/.ssh/authorized_keys",
"sudo grep -q '^AllowUsers myansibleuser' /etc/ssh/sshd_config || echo 'AllowUsers myansibleuser' | sudo tee -a /etc/ssh/sshd_config",
"sudo systemctl restart sshd"
]
}
}
variable "oci_user_opc_private_key" {
description = "Clé privée SSH temporairement utilisée pour se connecter avec l'utilisateur sudo opc"
type = string
sensitive = true
ephemeral = true
validation {
condition = length(var.oci_user_opc_private_key) > 10
error_message = "La taille de oci_user_opc_private_key est incorrecte."
}
}
Idéalement oci_user_opc_private_key sera récupérée d’un Vault.
Attributs de type secrets non éphémère
Dans le cas des ressources oci_database_db_system les attributs admin_password et tde_wallet_password sont des attributs de type secrets car leur valeur ne doit pas être exposée, s’ils sont exposés toute personne ayant accès au serveur de base de données oracle sera en mesure d’exécuter des actions admin s’il est en possession de ces valeur, … Donc par exemple un développeur qui aurait accès au tfstate d’une certaine manière pourrait devenir administrateur de la base de données !
Or ces secrets peuvent être modifiés via le provider oci, le provider oci a besoin de connaître la valeur de ces secrets pour déterminer lors d’un 2e terraform apply / plan (ou lors d’un n-ième terraform apply / plan) si ces password ont changés et s’ils font l’objet d’un update.
Par conséquent ils sont écrits dans le tfstate et donc exposés.
Pour parer cela, la notion d’attributs write only a été introduite.
Les attributs write only (supporté à partir de Terrafor v1.11+)
« The Vault provider has introduced write-only attributes (supported from Terraform v1.11+) for a more secure way to manage data. »
Un attribut write only ne sera pas écrit dans le plan ni le state (tfplan et tfstate). Cependant il faut que le provider gère cette notion pour que l’on puisse en bénéficier.
Par convention un attribut write only est un attribut suffixé par _wo
Dans le cas des ressources oci_database_db_system les attributs admin_password et tde_wallet_password sont des attributs de type secrets et aurait été de très bon candidat au type « write only », cependant ce n’est pas pris en charge pour le moment (le 31 juillet 2026).
S’ils avaient été géré par le provider, il le serait ainsi dans la déclaration de la ressource base de données :
admin_password_wo = var.module_admin_password admin_password_wo_version = 1 tde_wallet_password_wo = var.module_admin_password tde_wallet_password_wo_version = 1
Lorsqu’un provider gère un attribut write only, systématiquement il gère 2 attributs : un attribut suffixé par _wo et ce même attribut suffixé par _wo_version
Dès que l’on modifie un write only attribute alors on modifie également sa write only version associée.
Par exemple lors d’une mise à jour on aura :
admin_password_wo = newpassword1 admin_password_wo_version = 2 tde_wallet_password_wo = newpassword1 tde_wallet_password_wo_version = 2
Laisser un commentaire