Operations grimoire/Deploy with Terraform: Difference between revisions
No edit summary |
Per T2359. Explain the tf wrapper, and the directories where are stored the states/providers. Switch from tofu/terraform to tf command. |
||
| Line 1: | Line 1: | ||
== | == Overview == | ||
In addition to Salt, Nasqueron manages Infrastructure as Code using Terraform and OpenTofu. We deploy from [[Complector]] using <code>/opt/salt/nasqueron-operations</code> as our local copy of rOPS, authoritative for both Salt and Terraform. | |||
It's important to work from there to save a shared Terraform state. | It's important to work from there to save a shared Terraform state. | ||
We provide a <code>tf</code> wrapper that automatically runs OpenTofu or Terraform based on the directory and mounts the encrypted state directory on the fly. | |||
=== Paths === | |||
Terraform states are stored on a local backend. To protect sensitive infrastructure state (which often contains plaintext secrets and topology data) at rest, we use a split-architecture approach combining [[Operations grimoire/ZFS|ZFS]] and [[Operations grimoire/PEFS|PEFS]] (Private Encrypted File System). | |||
{| class="wikitable" | |||
|+ Paths for Terraform / OpenTofu deployment | |||
|- | |||
! Path | |||
! Type | |||
! Purpose | |||
! Filesystem Properties | |||
|- | |||
| <code>/opt/salt/nasqueron-operations/terraform/</code> | |||
| Standard Dir | |||
| Terraform/OpenTofu declarative code | |||
| Standard ZFS | |||
|- | |||
| <code>/opt/terraform.enc</code> | |||
| ZFS Dataset | |||
| Underlying encrypted block storage for PEFS | |||
| <code>compression=zstd</code> | |||
|- | |||
| <code>/opt/terraform/encrypted</code> | |||
| PEFS Mount | |||
| '''Encrypted state files.''' Mounted only during TF operations. | |||
| AES-256-XTS | |||
|- | |||
| <code>/opt/terraform/tf-data</code> | |||
| ZFS Dataset | |||
| '''Provider cache and modules.''' <code>TF_DATA_DIR</code> target. | |||
| <code>compression=lz4</code>, <code>recordsize=128K</code> (optimized for Go binaries) | |||
|} | |||
=== Access control === | |||
* PEFS operations (mounting, adding keys) are executed as the <code>deploy</code> user. | |||
* The wrapper verifies the invoking user belongs to the <code>ops</code> or <code>deploy</code> group before proceeding. | |||
* The PEFS passphrase to read the Terraform states encryption key is stored in HashiCorp Vault at <code>ops/infra/complector/terraform/pefs</code>. | |||
== Standard operations == | |||
The <code>tf</code> wrapper automatically mounts PEFS, fetches the decryption key from Vault, executes the command, and unmounts PEFS upon exit, even on failure or <code>Ctrl+C</code>. | |||
You can use it exactly like you would use terraform or tofu commands: | |||
<syntaxhighlight lang="bash"> | |||
$ cd /opt/salt/nasqueron-operations/terraform/ovh-ops-backups | |||
$ tf init | |||
$ tf plan | |||
$ tf apply | |||
</syntaxhighlight> | |||
== Specific deployment notes == | == Specific deployment notes == | ||
=== Maintenance of .tfstate files === | |||
For manual state surgery or moving files, use the explicit mount commands. These bypass the automatic unmount. | |||
<syntaxhighlight lang="bash"> | |||
# Mount PEFS and load keys for manual maintenance | |||
tf mount | |||
# Perform manual operations (e.g., moving state files) | |||
cd /opt/terraform/encrypted/tf-states | |||
# ... | |||
# Unmount when finished | |||
tf unmount | |||
</syntaxhighlight> | |||
=== Choose between opentofu or terraform === | |||
By default, tofu is used. | |||
The wrapper maintains an allowlist of project that still require the HashiCorp Terraform versions. | |||
The main blocker is <code>hashicorp/vault</code>, not compiled for FreeBSD on OpenTofu registry. | |||
To add or remove a project there, edit the <code>STILL_REQUIRE_TERRAFORM</code> variable in {{Ops file|roles/salt-primary/opentofu/files/tf.sh}}. | |||
=== New project configuration === | |||
Every Terraform project must explicitly configure its local backend to write state into the PEFS-encrypted mount. | |||
Add this to your project's <code>backend.tf</code> or <code>providers.tf</code>: | |||
<syntaxhighlight lang="hcl"> | |||
terraform { | |||
backend "local" { | |||
path = "/opt/terraform/encrypted/tf-states/<project-name>/terraform.tfstate" | |||
} | |||
} | |||
</syntaxhighlight> | |||
''Note: The provider cache path does not need to be configured in HCL. The wrapper automatically exports <code>TF_DATA_DIR=/opt/terraform/tf-data/<project-name></code>.'' | |||
=== Vault / OpenBao === | === Vault / OpenBao === | ||
==== General notes ==== | ==== General notes ==== | ||
;OpenTofu support | ;OpenTofu support | ||
As of 2026- | As of 2026-09-06, the Vault provider isn't compiled for FreeBSD. You need to use Terraform instead. | ||
;Vault | ;Vault | ||
| Line 22: | Line 115: | ||
$ cd /opt/salt/nasqueron-operations/terraform/openbao | $ cd /opt/salt/nasqueron-operations/terraform/openbao | ||
$ | $ tf init # if you've a new entry requiring a module, it needs to be installed | ||
$ | $ tf plan | ||
$ | $ tf apply | ||
</syntaxhighlight> | </syntaxhighlight> | ||
| Line 41: | Line 134: | ||
<syntaxhighlight lang="shell"> | <syntaxhighlight lang="shell"> | ||
$ cd /opt/salt/nasqueron-operations/terraform/openbao | $ cd /opt/salt/nasqueron-operations/terraform/openbao | ||
$ | $ tf init # if you've a new entry requiring a module, it needs to be installed | ||
$ | $ tf plan | ||
$ | $ tf apply | ||
$ cd /opt/salt/nasqueron-operations | $ cd /opt/salt/nasqueron-operations | ||
| Line 81: | Line 174: | ||
You'll need to get application key, application secret and consumer_key in your $HOME/.ovh.conf on Complector. | You'll need to get application key, application secret and consumer_key in your $HOME/.ovh.conf on Complector. | ||
You can also generate such files with <code>ovhcloud login</code>. | You can also generate such files with <code>ovhcloud login</code>. | ||
== First Terraform configuration (DRP) == | == First Terraform configuration (DRP) == | ||
| Line 107: | Line 186: | ||
# Ensure pefs is active with <code>kldstat | grep pefs</code> | # Ensure pefs is active with <code>kldstat | grep pefs</code> | ||
# Ensure Vault is up, then <code>vault kv put ops/infra/complector/terraform/pefs password=$(openssl rand -hex 48)</code> | # Ensure Vault is up, then <code>vault kv put ops/infra/complector/terraform/pefs password=$(openssl rand -hex 48)</code> | ||
# Run <code>initialize-pefs ops/infra/complector/terraform/pefs /opt/terraform.enc /opt/terraform</code> | # Run <code>initialize-pefs ops/infra/complector/terraform/pefs /opt/terraform.enc /opt/terraform/encrypted</code> | ||
The vault previous version of the secret, if still available, is for the previous directory, keep it if we restore afterhand. | The vault previous version of the secret, if still available, is for the previous directory, keep it if we restore afterhand. | ||
| Line 115: | Line 194: | ||
=== Error: Module not installed === | === Error: Module not installed === | ||
You need to run <code> | You need to run <code>tf init</code> to prepare for any new provider. | ||
<syntaxhighlight lang="shell"> | <syntaxhighlight lang="shell"> | ||
$ | $ tf plan | ||
│ Error: Module not installed | │ Error: Module not installed | ||
| Line 131: | Line 210: | ||
<syntaxhighlight lang="shell"> | <syntaxhighlight lang="shell"> | ||
$ | $ tf init | ||
Initializing the backend... | Initializing the backend... | ||
| Line 149: | Line 228: | ||
On OpenTofu, some modules are only compiled for Linux, not for FreeBSD. | On OpenTofu, some modules are only compiled for Linux, not for FreeBSD. | ||
Switch to Terraform pending a solution to help the OpenTofu builds. | Switch to Terraform pending a solution to help the OpenTofu builds. | ||
To allow the wrapper to run <code>terraform</code> commands, add your project directory to the <code>STILL_REQUIRE_TERRAFORM</code> variable in {{Ops file|roles/salt-primary/opentofu/files/tf.sh}}. | |||
=== State rollback === | === State rollback === | ||
| Line 154: | Line 235: | ||
When declaring new Terraform resources, it's possible to remove from the state an item, without actually destroying it at infrastructure level: | When declaring new Terraform resources, it's possible to remove from the state an item, without actually destroying it at infrastructure level: | ||
$ | $ tf state rm <state name> | ||
Removed ….… | Removed ….… | ||
Successfully removed 1 resource instance(s). | Successfully removed 1 resource instance(s). | ||
Revision as of 18:54, 6 September 2026
Overview
In addition to Salt, Nasqueron manages Infrastructure as Code using Terraform and OpenTofu. We deploy from Complector using /opt/salt/nasqueron-operations as our local copy of rOPS, authoritative for both Salt and Terraform.
It's important to work from there to save a shared Terraform state.
We provide a tf wrapper that automatically runs OpenTofu or Terraform based on the directory and mounts the encrypted state directory on the fly.
Paths
Terraform states are stored on a local backend. To protect sensitive infrastructure state (which often contains plaintext secrets and topology data) at rest, we use a split-architecture approach combining ZFS and PEFS (Private Encrypted File System).
| Path | Type | Purpose | Filesystem Properties |
|---|---|---|---|
/opt/salt/nasqueron-operations/terraform/
|
Standard Dir | Terraform/OpenTofu declarative code | Standard ZFS |
/opt/terraform.enc
|
ZFS Dataset | Underlying encrypted block storage for PEFS | compression=zstd
|
/opt/terraform/encrypted
|
PEFS Mount | Encrypted state files. Mounted only during TF operations. | AES-256-XTS |
/opt/terraform/tf-data
|
ZFS Dataset | Provider cache and modules. TF_DATA_DIR target.
|
compression=lz4, recordsize=128K (optimized for Go binaries)
|
Access control
- PEFS operations (mounting, adding keys) are executed as the
deployuser. - The wrapper verifies the invoking user belongs to the
opsordeploygroup before proceeding. - The PEFS passphrase to read the Terraform states encryption key is stored in HashiCorp Vault at
ops/infra/complector/terraform/pefs.
Standard operations
The tf wrapper automatically mounts PEFS, fetches the decryption key from Vault, executes the command, and unmounts PEFS upon exit, even on failure or Ctrl+C.
You can use it exactly like you would use terraform or tofu commands:
$ cd /opt/salt/nasqueron-operations/terraform/ovh-ops-backups
$ tf init
$ tf plan
$ tf apply
Specific deployment notes
Maintenance of .tfstate files
For manual state surgery or moving files, use the explicit mount commands. These bypass the automatic unmount.
# Mount PEFS and load keys for manual maintenance
tf mount
# Perform manual operations (e.g., moving state files)
cd /opt/terraform/encrypted/tf-states
# ...
# Unmount when finished
tf unmount
Choose between opentofu or terraform
By default, tofu is used.
The wrapper maintains an allowlist of project that still require the HashiCorp Terraform versions.
The main blocker is hashicorp/vault, not compiled for FreeBSD on OpenTofu registry.
To add or remove a project there, edit the STILL_REQUIRE_TERRAFORM variable in rOPS: roles/salt-primary/opentofu/files/tf.sh.
New project configuration
Every Terraform project must explicitly configure its local backend to write state into the PEFS-encrypted mount.
Add this to your project's backend.tf or providers.tf:
terraform {
backend "local" {
path = "/opt/terraform/encrypted/tf-states/<project-name>/terraform.tfstate"
}
}
Note: The provider cache path does not need to be configured in HCL. The wrapper automatically exports TF_DATA_DIR=/opt/terraform/tf-data/<project-name>.
Vault / OpenBao
General notes
- OpenTofu support
As of 2026-09-06, the Vault provider isn't compiled for FreeBSD. You need to use Terraform instead.
- Vault
You need a Vault token to allow the provider to connect.
You also need to set VAULT_ADDR to https://172.27.27.7:8200 as Vault doesn't listen on 127.0.0.1
$ export VAULT_ADDR=https://172.27.27.7:8200
$ sudo /opt/salt/nasqueron-operations/utils/vault/issue-admin-token.py > ~/.vault-token
$ cd /opt/salt/nasqueron-operations/terraform/openbao
$ tf init # if you've a new entry requiring a module, it needs to be installed
$ tf plan
$ tf apply
It could be interesting to maintain an alternative policy to admin for the Terraform provider, restricted to the current actions. Note benefits would be limited as those include policies management.
Propagate secrets (DRP)
- No automatic secret rotation
Secrets rotation is disabled with a lifecycle management ignore_changes = [ secret_id, ].
To rotate a secret, it needs first to be destroyed from terraform state:
terraform destroy -target=module.viperserv_approle.vault_approle_auth_backend_role_secret_id.this
- Full procedure
Once the AppRole have been created in Vault, they need to be provisioned to the relevant configuration files.
$ cd /opt/salt/nasqueron-operations/terraform/openbao
$ tf init # if you've a new entry requiring a module, it needs to be installed
$ tf plan
$ tf apply
$ cd /opt/salt/nasqueron-operations
$ salt windriver state.sls_id /usr/local/etc/secrets/rhyne-wyse.yaml roles/reports/rhyne-wyse/config
$ salt windriver state.sls_id /srv/viperserv/.credentials roles/viperserv/eggdrop/config
# Missing for router
Don't forget to always apply this full procedure.
CARP OVH secrets usage
The OVH credentials used by the CARP failover script are stored in Vault at:
apps/network/carp-hyper-001-switch
These credentials are accessed by router-002 and router-003. They are then used by a script triggered by devd to update the OVH failover IP MAC address depending on the CARP state (MASTER/BACKUP). The script authenticates to Vault using AppRole (via secretsmith) to retrieve them.
The AppRole credentials (role_id and secret_id) used by the script are stored in:
ops/secrets/network/router/vault
Vault secrets are provisioned using Terraform (openbao module), and must be propagated to the routers using Salt.
The Vault Secrets may need to be rotated, so we need a simple way to propagate updated credentials to routers. This is enabled by the mechanism introduced in D4026.
ovh-ops-backups
This configuration is handled by Terraform.
It requires a full access to public cloud project for backups, as the project is fully configured by those Terraform files.
You'll need to get application key, application secret and consumer_key in your $HOME/.ovh.conf on Complector.
You can also generate such files with ovhcloud login.
First Terraform configuration (DRP)
Initialize Terraform working directories
Terraform and OpenTofu are currently deployed from Complector. The working directories are encrypted, a wrapper decrypt them for each operation.
You need to restore from a backup the content of /opt/terraform.enc or if not available reinitialize a new encrypted directory. Currently, the procedure is valid for FreeBSD, as it requires PEFS to encrypt working director.
- On the salt primary server, deploy the full roles/salt-primary, or at least the roles/salt-primary/opentofu unit
- Ensure pefs is active with
kldstat | grep pefs - Ensure Vault is up, then
vault kv put ops/infra/complector/terraform/pefs password=$(openssl rand -hex 48) - Run
initialize-pefs ops/infra/complector/terraform/pefs /opt/terraform.enc /opt/terraform/encrypted
The vault previous version of the secret, if still available, is for the previous directory, keep it if we restore afterhand. But you need a new password for your new fresh encrypted directory.
Troubleshoot
Error: Module not installed
You need to run tf init to prepare for any new provider.
$ tf plan
│ Error: Module not installed
│
│ on rhyne_wyse.tf line 23:
│ 23: module "rhyne_wyse_approle" {
│
│ This module is not yet installed. Run "tofu init" to install all modules required by this configuration.
Error: Incompatible provider version
$ tf init
Initializing the backend...
Initializing modules...
- rhyne_wyse_approle in modules/app_credentials
Initializing provider plugins...
- Finding hashicorp/vault versions matching "5.3.0"...
╷
│ Error: Incompatible provider version
│
│ Provider registry.opentofu.org/hashicorp/vault v5.3.0 does not have a package available for your current platform, freebsd_amd64.
│
│ Provider releases are separate from OpenTofu CLI releases, so not all providers are available for all platforms. Other versions of this provider may have different platforms supported.
On OpenTofu, some modules are only compiled for Linux, not for FreeBSD. Switch to Terraform pending a solution to help the OpenTofu builds.
To allow the wrapper to run terraform commands, add your project directory to the STILL_REQUIRE_TERRAFORM variable in rOPS: roles/salt-primary/opentofu/files/tf.sh.
State rollback
When declaring new Terraform resources, it's possible to remove from the state an item, without actually destroying it at infrastructure level:
$ tf state rm <state name> Removed ….… Successfully removed 1 resource instance(s).
