Operations grimoire/Deploy with Terraform: Difference between revisions

From Nasqueron Agora
Yousra (talk | contribs)
Proofread
 
(21 intermediate revisions by 2 users not shown)
Line 1: Line 1:
== Where to work? ==
== Overview ==
We deploy from [[Complector]] using <code>/opt/salt/nasqueron-operations</code> as our local copy of rOPS, authoritative for both Salt and Terraform


You need to belong to the <code>ops</code> group to be able to have access and have write-rights on the repository
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.
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.
 
<div class="alert">It's important to work on Complector, so that all deployments use the shared Terraform state.</div>
 
=== 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 files.''' <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 that the invoking user belongs to the <code>ops</code> or <code>deploy</code> group before proceeding.
* The PEFS passphrase 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 projects that still require HashiCorp Terraform.
The main blocker is <code>hashicorp/vault</code>, not compiled for FreeBSD on OpenTofu registry.
To add or remove a project from this list, 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-02-07, the Vault provider isn't compiled for FreeBSD. You need to use Terraform instead.
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
$ terraform init # if you've a new entry requiring a module, it needs to be installed
$ tf init # install any newly required providers and modules
$ terraform plan
$ tf plan
$ terraform apply
$ tf apply
</syntaxhighlight>
</syntaxhighlight>


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.
It could be interesting to maintain an alternative policy to admin for the Terraform provider, restricted to the current actions. Note that the benefits would be limited as those include policies management.


==== Propagate secrets (DRP) ====
==== Propagate secrets (DRP) ====
Line 37: Line 130:


;Full procedure  
;Full procedure  
Once the AppRole have been created in Vault, they need to be provisioned to the relevant configuration files.
Once the AppRole has been created in Vault, its credentials need to be provisioned to the relevant configuration files.


<syntaxhighlight lang="shell">
<syntaxhighlight lang="shell">
$ cd /opt/salt/nasqueron-operations/terraform/openbao
$ cd /opt/salt/nasqueron-operations/terraform/openbao
$ terraform init # if you've a new entry requiring a module, it needs to be installed
$ tf init # if you've a new entry requiring a module, it needs to be installed
$ terraform plan
$ tf plan
$ terraform apply
$ tf apply


$ cd /opt/salt/nasqueron-operations
$ cd /opt/salt/nasqueron-operations
Line 51: Line 144:
</syntaxhighlight>
</syntaxhighlight>


<div class="alert">Important. Each time you'll reprovision the secrets, they will change.
<div class="alert">Important: Each time you reprovision the secrets, they will change.


Don't forget to always apply this full procedure.
Don't forget to always apply this full procedure.
</div>
</div>


=== CARP OVH secrets usage ===
==== CARP OVH secrets usage ====


The OVH credentials used by the CARP failover script are stored in Vault at:
The OVH credentials used by the CARP failover script are stored in Vault at:
Line 62: Line 155:
'''apps/network/carp-hyper-001-switch'''
'''apps/network/carp-hyper-001-switch'''


These credentials are accessed by router-002 and router-003  and are used by a script triggered by devd to update the OVH failover IP MAC address depending on the CARP state (MASTER/BACKUP, that is using AppRole authentication (via secretsmith),
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.


Secrets are provisioned using Terraform (openbao module), and must be propagated to the routers using Salt.
The AppRole credentials (role_id and secret_id) used by the script are stored in:


When rotating these secrets:
'''ops/secrets/network/router/vault'''
* Terraform must be applied again


Failing to do so may cause CARP failover to break due to invalid OVH credentials.
Vault secrets are provisioned using Terraform (openbao module), and must be propagated to the routers using Salt.  


== Table of Terraform states ==
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.


{| class="wikitable"
=== ovh-ops-backups ===
|+ Terraform and OpenBao states
This configuration is handled by Terraform.
|-
 
! Configuration !! State back-end !! Path !! Software to use
It requires full access to the public cloud project for backups, as the project is fully configured by these Terraform files.
|-
 
| openbao || On disk || /opt/salt/nasqueron-operations/terraform/openbao/terraform.tfstate || Terraform
You'll need to get the application key, application secret, and consumer key and write them in your $HOME/.ovh.conf on Complector.
|}
You can also generate the file with <code>ovhcloud login</code>.
 
== First Terraform configuration (DRP) ==
=== Initialize Terraform working directories ===
Terraform and OpenTofu projects are currently deployed from Complector.
The states are encrypted, and a wrapper decrypts them for each operation.
 
You need to restore the contents of /opt/terraform.enc from a backup, or if it is not available, initialize a new encrypted directory.
Currently, the procedure is specific to FreeBSD, as it requires PEFS to encrypt directories.
 
# 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 <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>
# Run <code>initialize-pefs ops/infra/complector/terraform/pefs /opt/terraform.enc /opt/terraform/encrypted</code>


On disk paths are stored in Complector.
The previous version of the Vault secret, if still available, belongs to the previous directory; keep it if you need to restore that directory. However, you need a new password when creating a fresh encrypted directory.


== Troubleshoot ==
== Troubleshoot ==
=== Error: Module not installed ===
=== Error: Module not installed ===


You need to run <code>tofu init</code> to prepare for any new provider.
You need to run <code>tf init</code> to prepare for any new provider.


<syntaxhighlight lang="shell">
<syntaxhighlight lang="shell">
$ tofu plan
$ tf plan


│ Error: Module not installed
│ Error: Module not installed
Line 102: Line 209:


<syntaxhighlight lang="shell">
<syntaxhighlight lang="shell">
$ tofu init
$ tf init


Initializing the backend...
Initializing the backend...
Line 118: Line 225:
</syntaxhighlight>
</syntaxhighlight>


On OpenTofu, some modules are only compiled for Linux, not for FreeBSD.
On OpenTofu, some providers 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 ===
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).


[[Category:Operations grimoire]]
[[Category:Operations grimoire]]
[[Category:Terraform]]
[[Category:Terraform]]

Latest revision as of 21:08, 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.

We provide a tf wrapper that automatically runs OpenTofu or Terraform based on the directory and mounts the encrypted state directory on the fly.

It's important to work on Complector, so that all deployments use the shared Terraform state.

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).

Paths for Terraform / OpenTofu deployment
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 files. TF_DATA_DIR target. compression=lz4, recordsize=128K (optimized for Go binaries)

Access control

  • PEFS operations (mounting, adding keys) are executed as the deploy user.
  • The wrapper verifies that the invoking user belongs to the ops or deploy group before proceeding.
  • The PEFS passphrase 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 projects that still require HashiCorp Terraform.

The main blocker is hashicorp/vault, not compiled for FreeBSD on OpenTofu registry.

To add or remove a project from this list, 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 # install any newly required providers and modules
$ 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 that the 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 has been created in Vault, its credentials 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
Important: Each time you reprovision the secrets, they will change.

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 full access to the public cloud project for backups, as the project is fully configured by these Terraform files.

You'll need to get the application key, application secret, and consumer key and write them in your $HOME/.ovh.conf on Complector. You can also generate the file with ovhcloud login.

First Terraform configuration (DRP)

Initialize Terraform working directories

Terraform and OpenTofu projects are currently deployed from Complector. The states are encrypted, and a wrapper decrypts them for each operation.

You need to restore the contents of /opt/terraform.enc from a backup, or if it is not available, initialize a new encrypted directory. Currently, the procedure is specific to FreeBSD, as it requires PEFS to encrypt directories.

  1. On the salt primary server, deploy the full roles/salt-primary, or at least the roles/salt-primary/opentofu unit
  2. Ensure pefs is active with kldstat | grep pefs
  3. Ensure Vault is up, then vault kv put ops/infra/complector/terraform/pefs password=$(openssl rand -hex 48)
  4. Run initialize-pefs ops/infra/complector/terraform/pefs /opt/terraform.enc /opt/terraform/encrypted

The previous version of the Vault secret, if still available, belongs to the previous directory; keep it if you need to restore that directory. However, you need a new password when creating a 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 providers 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).