This is the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

Tf modules

Reusable Terraform modules for Vault, virtual machines, and storage

This project contains reusable Terraform modules for Vault configuration, Proxmox virtual machines, backup storage, and related infrastructure. The module collection is still in progress.

  • Terraform modules

1 -

dns-records Specification

Provide one canonical DNS transformation for project-local Terraform resources and offline inspection, preserving declared names, values, and view ownership.

The module SHALL accept decoded owner DNS documents and a zone, flatten every supported type member, expand and deduplicate destinations, and expose one normalized map without provider configuration. It SHALL reject unsupported types, destinations, malformed members, and absolute names outside the zone.

  • WHEN an entry contains A and AAAA with destinations all and global
  • THEN normalization produces exactly four records, one per type and view

Normalized and provider resource keys SHALL use logical declaration key, type, and view without mutable record values. Separate logical keys SHALL preserve multiple records at the same name and type, including MX priority and TXT values. Explicit TTLs SHALL override the type-specific compatibility defaults.

  • WHEN an A member changes its address without changing its logical key
  • THEN its normalized and provider resource keys remain unchanged

The module SHALL accept provider instances from its caller and create individual Cloudflare global and RouterOS dc1 records only when explicitly enabled. It SHALL default to disabled provider ownership while preserving normalized outputs. Cloudflare records SHALL remain unproxied. Disabled ownership SHALL NOT imply that operational provider initialization or credential prerequisites are suppressed.

  • WHEN a root calls the module without enabling ownership
  • THEN it declares no provider record resources and still exposes every normalized declaration

2 -

project-dns Specification

Record the retirement of this project’s landing DNS infrastructure while preserving its repository documentation and builds.

The project SHALL have no dedicated landing DNS declaration, Terraform root, or operational source export. Its landing page SHALL be published by the main site under /projects/tf_modules/ instead of a dedicated hostname.

  • WHEN the project tree is consumed
  • THEN it contains no landing DNS declaration, Terraform DNS stage, or landing build target, and the main site owns its landing page.

3 -

Reusable Terraform modules

Provide reusable infrastructure declarations for Vault, virtual machines, storage, and related services. This baseline was observed at repository revision 550d7e79 on 2026-09-08. The collection remains in progress according to its README. Sources for the representative module contracts below are the AppRole module, transit-key module, backup-bucket module, and module packaging. These are declarative source contracts; no live provisioning is asserted.

The AppRole, transit-key, backup-bucket, and Proxmox VM module packages SHALL each expose their main.tf through a same-named Bazel filegroup available to repository subpackages.

  • WHEN a target depends on //projects/tf_modules/vault_approle:vault_approle
  • THEN its declared source input includes the AppRole module’s main.tf

The AppRole module SHALL declare an identity entity, backend alias, internal group, and named AppRole with configurable token and secret-ID limits. Its role policies SHALL combine the shared and AppRole-secret policies with caller policies and the Yandex-folder policy unless that policy is disabled for the role.

  • WHEN disable_yc_folder_policy is true
  • THEN the role’s policy list excludes the module’s Yandex-folder policy while preserving shared, AppRole-secret, and caller policies

The transit-key module SHALL declare a named Vault transit key, an encryption policy and group, and a decryption policy and group. Decryptor member groups SHALL also be included in the encryption group.

  • WHEN a caller adds a group ID to decryptors_member_group_ids
  • THEN the module includes that group in both transit permission groups

The backup-bucket module SHALL declare a versioned Yandex storage bucket with default KMS encryption, grant read and write permissions to the supplied service accounts, and store bucket identifiers in the caller-selected Vault KV v2 location.

  • WHEN the caller supplies the bucket name components, folder, service accounts, and Vault destination
  • THEN the declarations bind bucket encryption to the module’s KMS key and publish the bucket ID, folder ID, and bucket name to that Vault destination

4 - Backup bucket

S3 bucket for backups

5 - DNS records

Canonical DNS declarations for Cloudflare and RouterOS

This reusable module translates an owner’s decoded dnsconfig.json through its provider-free normalizer, then manages individual global Cloudflare and dc1 RouterOS records with caller-provided providers. Owners with only global records use the global entrypoint, which needs only Cloudflare. The combined entrypoint delegates Cloudflare resources to that same module and consumes its normalization for RouterOS. Repository infrastructure roots may consume its Bazel source filegroups; the child normalizer also supports provider-free inspection.

module "dns" {
  source             = "../../../projects/tf_modules/dns_records"
  document           = jsondecode(file("${path.module}/../dnsconfig.json"))
  cloudflare_zone_id = var.dns_cloudflare_zone_id
  enabled            = var.dns_enabled
}

zone defaults to alwaldend.com. An explicit cloudflare_zone_id is optional; when it is null or empty, enabled global records resolve exactly one Cloudflare zone matching zone. The token must permit zone listing and reading. Disabled ownership and declarations without global records skip this lookup. enabled defaults to false so a prepared root owns no provider records. Keep it true after adoption: setting it false in a state that already owns records plans deletion and is not a rollback procedure. Disabling ownership does not suppress provider initialization. Operational Terraform commands still require the caller’s real provider configuration and the owning Vault/AppRole prerequisites. In particular, the pinned RouterOS provider probes its configured API even when no DNS records are enabled. Use the provider-free normalizer for offline inspection. The migration runbook owns cutover, import, and rollback ordering. This module supplies no provider credentials or state backend.

The input contains exactly one records object. Each stable logical key contains one or more scalar type members and a nonempty dsp list. Supported destinations are global, dc1, and all; repeated destinations are deduplicated.

Type Required member fields Default TTL
A name, address 300
AAAA name, address 600
CNAME name, target 600
NS name, address 300
MX name, target, priority 300
TXT name, content 300

Every member accepts an integer ttl from 60 through 86400 seconds. MX priorities range from 0 through 65535. Multiple scalar values at the same name and type use distinct logical keys; changing values never changes resource identity. Unsupported fields and malformed members are rejected.

Names accept @, relative names, or zone-qualified names. Absolute owner names ending with a dot must be inside the zone. CNAME, MX, and NS targets accept @, relative names, zone-qualified names, or absolute external names ending with a dot. Normalization lowercases domain names and omits final dots; address and TXT bytes are preserved. relative_name is @ at the apex.

normalized_records maps logical_key/type/view to key, name, relative_name, type, value, priority (null except MX), ttl, view, and proxied (always false). This output remains available while disabled. The same keys index module.global.cloudflare_dns_record.records and module.dc1[0].routeros_ip_dns_record.records in the combined entrypoint; the global-only entrypoint uses cloudflare_dns_record.records. import_addresses exposes these addresses relative to the selected module, including while staged. The pinned providers import Cloudflare records by zone_id/record_id and RouterOS records by static IDs such as *1A.

Terraform tests use mocked providers and pinned local provider packages. The global-only test root has no RouterOS provider dependency. Normalization tests require no providers. Both run without network access; they establish declaration and resource mapping behavior, not live adoption.

5.1 - DNS normalization

Provider-free canonical DNS declaration transformation

This child module owns the canonical transformation described by the DNS module. Its document and zone inputs produce normalized_records without a provider, backend, or managed resources. Terraform writers and provider-free inspection consume this output.

5.2 - DNS test providers

Pinned provider packages for offline Terraform tests

The test target uses the public rules_terraform wrapper with the same Cloudflare, RouterOS, and Proxmox provider labels as infrastructure targets. The shared extension owns provider versions, release URLs, and checksums. Bazel fetches the declared archives before tests execute; Terraform uses the wrapper’s packaged filesystem mirror without a registry fallback.

Each regression runs Terraform in a caller-owned temporary module directory. Tests retain an explicit environment without inherited provider credentials; provider installation and mocked lifecycles require no network access.

The targeted import regression matches the owning Proxmox provider block by omitting pm_api_url. It checks both empty state and existing Proxmox resource state with no credentials. Without PM_API_URL, provider validation fails before target pruning; supplying only a loopback endpoint permits the DNS operation. The endpoint must receive zero requests.

The test imports a built-in Terraform resource through a root map into module.dns, checks the exact no-op import, applies that saved plan, and verifies the next targeted plan has no changes. Existing Proxmox resource attributes remain unchanged. An untargeted control must fail authentication before contacting the endpoint. These checks cover provider selection and state preservation; live DNS adoption retains its separate inventory and plan checks.

5.3 - Global DNS records

Cloudflare-only entrypoint for canonical DNS declarations

Global-only owners use this entrypoint with the shared module API to avoid requiring RouterOS configuration. The combined module also delegates its Cloudflare resources here, so both entrypoints share one implementation. Normalization returns all declared views, but this entrypoint owns only global records. Mixed-view owners use the parent module to manage dc1 as well.

Enabled global records use the explicit cloudflare_zone_id when supplied. Otherwise, the pinned cloudflare_zone data source resolves the configured zone name and rejects missing or ambiguous matches. This requires zone-list and zone-read access in addition to record permissions. Disabled ownership and declarations without global records perform no zone lookup.

The optional zone ID must be known during planning so Terraform can select whether discovery is needed. The repository’s AL injection supplies a known value or an empty string before Terraform starts. Zone-name discovery uses the normalizer’s canonical name, including case and final-dot handling.

import_addresses contains global addresses relative to this entrypoint. record_ids exposes bound Cloudflare IDs for adoption and identity checks.

6 - Pve vm qemu

Create a proxmox vm

7 - Releases

Releases

7.1 - head

Release head

8 - Terraform backend

Module that sets up an S3 terraform backend

9 - Vault approle

Create a vault approle

10 - Vault approle

Create a vault approle

11 - Vault OIDC provider

Vault OIDC provider

12 - Vault pki server

Create a pki config for a server

13 - Vault ssh server role

Create an ssh role for a server

14 - Vault transit key

Vault transit key

15 - Yc folder

Folder for Yandex Cloud