Repository navigation
Settings Reference
New to the tools? Read What is infrastructure as code first. This page is a lookup table, not a lesson.
This page lists every setting you can change in this repository, where each one lives, and what values it accepts. It is generated from one machine-readable file, docs/settings.json, and tests check that file against the code, so a variable cannot be added without being listed here.
| Place | What it holds | In git? | Example here |
|---|---|---|---|
A variable (variables.tf) |
The name, type, description, validation and default of a setting. This is the definition, not the value for your site. | Yes |
region, vpc_ip_range
|
A value file (terraform.tfvars) |
The values for your site. Ignored by git (.gitignore covers *.tfvars, *.tfvars.json, *.auto.tfvars.json and *.tfplan), so a real value never lands in a public repository by accident. A committed terraform.tfvars.example shows the shape with documentation-range values only. |
No (the .example file, yes) |
a private copy of the repository keeps its own |
Environment variables (TF_VAR_*) |
The same values delivered to a run, from the Spacelift stack environment (non-secret) or from Doppler (private values and secrets). | No | TF_VAR_admin_user_ids |
cost.json and approved.json
|
Droplet sizes and the approved spend. The cost gate reads them, so the size lives there and nowhere else. | Yes | stacks/10-access/cost.json |
Two rules follow. A secret is never a variable default and never in a value file: a value file is a plain file with no access control, and it is copied to laptops, chats and shell history. Secrets live in Doppler and arrive as an environment variable for one run. And a setting that is hard-coded today is listed too, so you can see it is not yet a knob.
Related pages: OpenTofu, stacks and modules (what a stack and a module are), Doppler and secrets (how a secret reaches a run), Fork, private stacks and the pipeline (keeping your real values in a private copy), Terraform reference (each declaration explained).
How to read the tables. Status is exists (in the code today) or planned (in the stage 2 design, not built yet). Sensitivity is public (safe for anyone), private (not a credential, but it identifies the owner's network or accounts) secret (a credential) or bootstrap-credential (a short-lived bearer token that has to be rendered into user_data so a machine can pull its own secrets once at first boot; it is a credential, not a plain private value, and only the log shipping bootstrap token has this class). Examples use documentation ranges (192.0.2.0/24, 198.51.100.0/24, 203.0.113.0/24), private ranges, and example.com only; none is a real value.
Stack variables are the settings you set per site. Module variables are filled in by the calling stack; they are listed so the table is complete, and you normally leave them alone.
| Setting | Type | Default | Validation | Set where | Example | Sensitivity | Status |
|---|---|---|---|---|---|---|---|
regionDigitalOcean region slug. Names are built from resource_key, which defaults to this. Check the sizes your account may use in the region (GET /v2/sizes) before changing it. |
string |
"tor1" |
^[a-z]{3}[0-9]$ (three lowercase letters and a digit); the pattern cannot know which sizes the account may use | tfvars (local plans) or Spacelift stack environment (TF_VAR_*) | tor1 |
public | exists |
vpc_ip_rangePrivate CIDR for the VPC. Must be the network address and must not overlap any range in reserved_ranges. |
string |
"10.78.0.0/24" |
private IPv4 CIDR (10/8, 172.16/12, 192.168/16, also checked in modules/vpc); the network address (10.78.0.0/24, not 10.78.0.5/24); prefix length between /16 and /28; no overlap with reserved_ranges (a precondition on the project, because OpenTofu 1.8 cannot compare two variables in a validation) | tfvars (local plans) or Spacelift stack environment (TF_VAR_*) | 10.99.0.0/24 |
private (not a credential, but identifies the owner) | exists |
project_nameName of the DigitalOcean project that groups the resources (the code has no description for it). |
string |
"clouds-against-humanity" |
none today (DigitalOcean's allowed character set is UNVERIFIED) | tfvars (local plans) or Spacelift stack environment (TF_VAR_*) | clouds-against-humanity |
public | exists |
reserved_rangesPrivate CIDRs the VPC range must not overlap (your homelab, other VPCs). Optional; they identify your network, so they are never committed. Prompts the human check, does not replace it. |
list(string) |
[] |
each entry an IPv4 CIDR; vpc_ip_range must not overlap any entry (compared by first and last address) | Spacelift stack environment (TF_VAR_reserved_ranges, a JSON list) or a private tfvars today; Doppler only once a hook line in .spacelift/config.yml requests it (the foundation hooks request only DIGITALOCEAN_TOKEN) | ["10.50.0.0/16", "10.60.0.0/16"] |
private (not a credential, but identifies the owner) | exists |
resource_keyKey used in the names and tags of cloud and Twingate objects instead of region; empty means region, so every name is identical to a stack that never set it. IMMUTABLE for the life of a stack: changing it changes names, causes widespread plan changes and may replace resources, including through tags or user_data. Never derived from site_name. |
string |
"" |
empty, or ^[a-z][a-z0-9-]{1,23}$; immutable on a live stack | tfvars (local plans) or Spacelift stack environment (TF_VAR_*) | tor1 |
public | exists |
site_nameTelemetry label for the site (metrics and log labels in a later stage); empty means site-<region>. In no resource name, tag or output; changing it replaces nothing. |
string |
"" |
empty, or ^[a-z][a-z0-9-]{1,23}$; unique across sites (a rule for the person, one stack cannot test it) | tfvars (local plans) or Spacelift stack environment (TF_VAR_*) | site-tor1 |
public | exists |
provider_nameLabel for the cloud provider; does not select modules. |
string |
"digitalocean" |
one of the implemented providers | tfvars (local plans) or Spacelift stack environment (TF_VAR_*) | digitalocean |
public | planned |
| Setting | Type | Default | Validation | Set where | Example | Sensitivity | Status |
|---|---|---|---|---|---|---|---|
host_firewallA default-drop host firewall (nftables table inet cah_host, input chain only) on each connector, a second layer behind the cloud firewall. Only loopback, established traffic, the ICMP and DHCP the network card needs, TCP 22 from host_firewall_ssh_sources and, when connector_telemetry.metrics_port is set, that port from the same sources are let in. Default false is the live state: merging code with this unset plans no change. true adds a script, a systemd unit and the nftables package through user_data, which REPLACES the droplet of every connector in host_firewall_instances (both by default). The script checks the ruleset, applies it, verifies sshd still listens and removes the table if not. Roll out one connector at a time: true with host_firewall_instances ["a"], apply, check SSH through Twingate, then ["a", "b"]. Not yet tested on a real machine. |
bool |
false |
none; non-nullable (null means false) | tfvars (local plans) or Spacelift stack environment (TF_VAR_*) | true |
public | exists |
host_firewall_instancesWhich connectors get host_firewall: a, b or both. Only matters once host_firewall is true. Set ["a"] first, apply, check SSH through Twingate to the new connector, then ["a", "b"]. A connector not listed runs without the host firewall, as it does today. |
list(string) |
["a", "b"] |
at least one entry; each entry is a or b, no repeats | tfvars (local plans) or Spacelift stack environment (TF_VAR_*) | ["a"] |
public | exists |
host_firewall_ssh_sourcesIPv4 CIDRs allowed to reach TCP 22 (and the connector metrics port) through the host firewall. Default the three private RFC 1918 blocks: the cloud firewall already limits SSH to the connector tag and this only narrows it to private space, because the module does not know the VPC CIDR. Narrow it to your VPC range if you know it. A /0 is refused on purpose: it would defeat the narrowing, and the cloud firewall stays the outer layer. Only matters once host_firewall is true. |
list(string) |
["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"] |
at least one IPv4 CIDR, no repeats; no leading zeros in octets or prefix; prefix 1 to 32 (/0 is refused on purpose); host bits and overlaps allowed | tfvars (local plans) or Spacelift stack environment (TF_VAR_*) | ["10.0.0.0/8"] |
public | exists |
node_exporterOpt-in node exporter (prometheus-node-exporter from the distro archive) on each connector, bound to its private IPv4 address only. Default {} (enabled false) is the live state: merging code with this unset plans no change. enabled = true adds a package, a script and a systemd drop-in through user_data, which REPLACES the droplet of every connector in node_exporter_instances (both by default). port defaults to 9100. Opens no firewall port by itself: set metrics_source_tag as well. Roll out one connector at a time, checking SSH through Twingate and the memory headroom of the 1 GB droplet. NOT applied and NOT tested on a real machine yet. |
object({enabled = optional(bool, false), port = optional(number, 9100)}) |
{} |
port a whole number from 1024 to 65535; non-nullable (null means {}) | tfvars (local plans) or Spacelift stack environment (TF_VAR_*) | { enabled = true, port = 9100 } |
public | exists |
node_exporter_instancesWhich connectors get node_exporter: a, b or both. Only matters once node_exporter.enabled is true. Set ["a"] first, apply, check SSH through Twingate to the new connector, then ["a", "b"]. A connector not listed runs without the exporter, as it does today. |
list(string) |
["a", "b"] |
at least one entry; each entry is a or b, no repeats | tfvars (local plans) or Spacelift stack environment (TF_VAR_*) | ["a"] |
public | exists |
metrics_source_tagDigitalOcean tag of the droplets that may scrape the connectors' metrics ports (the node exporter port and connector_telemetry.metrics_port, tcp). Empty (default) is the live state: no metrics rule exists. When set, the connector firewall gains one tag-sourced inbound rule (source_tags = [this tag]) per enabled metrics port: never an address, never a public source. The tag must already exist on the account. Changes the firewall only; replaces no droplet. |
string |
"" |
empty, or ^[A-Za-z0-9:_-]{1,255}$ (a tag name: an address or CIDR is refused) | tfvars (local plans) or Spacelift stack environment (TF_VAR_*) | cah-telemetry-tor1 |
public | exists |
log_shippingOpt-in log shipping: the Wazuh agent (from Wazuh's apt repository, key checked against apt_key_fingerprint) and auditd on each connector, enrolled at first boot with a secret pulled from Doppler. Default {} is the live state: merging code with this unset plans no change. enabled = true changes user_data and REPLACES the droplet of every connector in log_shipping_instances (both by default). Roll out one connector at a time, checking SSH through Twingate and the memory headroom of the 1 GB droplet (UNMEASURED). Required when enabled: manager_address (the manager as reached through Twingate), agent_name_prefix (agents are named <prefix>-connector-<a or b>; use the same value as site_name, but it is a separate input because it lands in user_data), apt_key_fingerprint (40 uppercase hex digits, no default on purpose) and log_shipping_bootstrap_token. manager_port 1514 and enroll_port 1515 must match the logging_servers ports in stack 20. The Twingate headless client the agent needs to reach the manager is NOT part of this change. NOT applied and NOT tested on a real machine. |
object({enabled = optional(bool, false), manager_address = optional(string, ""), manager_port = optional(number, 1514), enroll_port = optional(number, 1515), agent_name_prefix = optional(string, ""), agent_group = optional(string, ""), apt_key_fingerprint = optional(string, ""), agent_version = optional(string, "")}) |
{} |
ports whole numbers 1-65535 (checked even while disabled); agent_name_prefix empty or ^[a-z][a-z0-9-]{1,40}$ and required when enabled; manager_address required when enabled (letters, digits, dots, hyphens); apt_key_fingerprint required when enabled and ^[0-9A-F]{40}$; agent_group empty or letters, digits, dot, underscore, hyphen; agent_version empty or like 4.9.2-1; non-nullable (null means {}) | tfvars (local plans) or Spacelift stack environment (TF_VAR_*); the manager address is private | { enabled = true, manager_address = "siem.example.com", agent_name_prefix = "site-tor1", apt_key_fingerprint = "<40 uppercase hex characters>" } |
private (not a credential, but identifies the owner) | exists |
log_shipping_instancesWhich connectors get log_shipping: a, b or both. Only matters once log_shipping.enabled is true. Set ["a"] first, apply, check SSH through Twingate and the memory headroom, then ["a", "b"]. A connector not listed runs without the agent, as it does today. |
list(string) |
["a", "b"] |
at least one entry; each entry is a or b, no repeats | tfvars (local plans) or Spacelift stack environment (TF_VAR_*) | ["a"] |
public | exists |
log_shipping_bootstrap_tokenThe one secret-shaped value log shipping puts in user_data: a short-lived, read-only Doppler service token for the config cah-<resource_key>-hostboot, created out of band (not by OpenTofu). The first-boot script deletes the token file before using it. Required when log_shipping.enabled; never rendered otherwise. Sensitive, but note that sensitive hides a value from output and does not keep it out of state. |
string |
"" |
empty or 16-512 characters of letters, digits, dot, underscore, hyphen; required when log_shipping.enabled | Spacelift stack environment (TF_VAR_log_shipping_bootstrap_token), write-only; never a value file | "" |
bootstrap credential (a short-lived bearer token rendered into user_data for first boot) | exists |
regionDigitalOcean region slug, from the foundation stack's region output. Names are built from resource_key, which defaults to this. |
string |
required | ^[a-z]{3}[0-9]$ (three lowercase letters and a digit) | Spacelift stack dependency, mapped to a TF_VAR_* environment variable (output of stack 00) | tor1 |
public | exists |
vpc_idID of the VPC created by stack 00 (the code has no description for it). |
string |
required | none today (planned: non-empty) | Spacelift stack dependency, mapped to a TF_VAR_* environment variable (output of stack 00) | 00000000-0000-4000-8000-000000000000 |
public | exists |
project_idID of the DigitalOcean project created by stack 00 (the code has no description for it). |
string |
required | none today (planned: non-empty) | Spacelift stack dependency, mapped to a TF_VAR_* environment variable (output of stack 00) | 00000000-0000-4000-8000-000000000001 |
public | exists |
baseline_tagBaseline firewall tag created by stack 00; the connectors carry it. |
string |
required | starts with "cah-baseline-" | Spacelift stack dependency, mapped to a TF_VAR_* environment variable (output of stack 00) | cah-baseline-tor1 |
public | exists |
twingate_networkBare Twingate network subdomain (the tenant name). |
string |
required | ^[a-z0-9-]+$ (no URL, no quotes) | Doppler (config prd), exported as TF_VAR_* by the Spacelift hook (TWINGATE_NETWORK) | example-tenant |
private (not a credential, but identifies the owner) | exists |
admin_ssh_public_keyPublic SSH key for the cah-admin user on the connectors: '<algorithm> <key>' with an optional trailing comment of one word. The comment is dropped before cloud-init, so user_data does not depend on it. |
string |
required | ^(ssh-ed25519|ssh-rsa|ecdsa-sha2-nistp(256|384|521)) [A-Za-z0-9+/]+=*( [A-Za-z0-9@._+:=/-]{1,64})?$ (one optional comment word of safe characters; no line break or other whitespace); the same check in modules/twingate-bootstrap | Doppler (config prd), exported as TF_VAR_* by the Spacelift hook (ADMIN_SSH_PUBLIC_KEY) | ssh-ed25519 AAAAEXAMPLEONLYNOTAREALKEY |
private (not a credential, but identifies the owner) | exists |
admin_ssh_key_nameName of an SSH key already registered on the DigitalOcean account. |
string |
required | non-empty | Doppler (config prd), exported as TF_VAR_* by the Spacelift hook (ADMIN_SSH_KEY_NAME) | example-admin-key |
private (not a credential, but identifies the owner) | exists |
admin_user_idsTwingate user ids placed in the admin group. |
list(string) |
required | at least one entry | Doppler (config prd), exported as TF_VAR_* by the Spacelift hook (ADMIN_USER_IDS, a JSON list) | ["example-user-id"] |
private (not a credential, but identifies the owner) | exists |
resource_keyKey used in the names and tags of cloud and Twingate objects instead of region; empty means region, so every name is identical to a stack that never set it. IMMUTABLE for the life of a stack: changing it changes names, causes widespread plan changes and may replace resources, including through tags or user_data. Never derived from site_name. |
string |
"" |
empty, or ^[a-z][a-z0-9-]{1,23}$; immutable on a live stack | tfvars (local plans) or Spacelift stack environment (TF_VAR_*) | tor1 |
public | exists |
site_nameTelemetry label for the site (metrics and log labels in a later stage); empty means site-<region>. In no resource name, tag or output; changing it replaces nothing. |
string |
"" |
empty, or ^[a-z][a-z0-9-]{1,23}$ | tfvars (local plans) or Spacelift stack environment (TF_VAR_*) | site-tor1 |
public | exists |
provider_nameLabel for the cloud provider; does not select modules. |
string |
"digitalocean" |
one of the implemented providers | tfvars (local plans) or Spacelift stack environment (TF_VAR_*) | digitalocean |
public | planned |
reboot_timesReboot time per connector (HH:MM, America/New_York, the droplet's local time). Keys a and b. The defaults are the times the live connectors use. |
map(string) |
{"a": "04:00", "b": "05:00"} |
exactly the keys a and b; each HH:MM in 24-hour time; the two reboot times at least 60 minutes apart, measured around the clock across midnight, which also keeps each connector's weekly update (reboot + 30 minutes) at least 30 minutes from the other's reboot | tfvars (local plans) or Spacelift stack environment (TF_VAR_*) | { a = "04:00", b = "05:00" } |
public | exists |
timezoneIANA time zone of the machines, also the zone reboot_time(s) and the weekly connector update are read in. Replaces a literal in the cloud-init template; the default renders byte-identical user_data. |
string |
"America/New_York" |
^(UTC|[A-Za-z_]+(/[A-Za-z0-9_+-]+){1,2})$ (an IANA-shaped name; line breaks, spaces and YAML-special characters are rejected) and the name must be listed in modules/twingate-bootstrap/timezones.txt (generated by scripts/gen_timezones.py; enforced in the bootstrap module, which every stack goes through). Changing it changes user_data, which is force-new and replaces the droplets | tfvars (local plans) or Spacelift stack environment (TF_VAR_*) | America/New_York |
public | exists |
connector_telemetry_previousWhat the connectors NOT listed in connector_telemetry_instances keep: the telemetry they run today. Default {} is right for the first activation. For a later change (say log_level 5 to 7) set connector_telemetry to the new value, this to the old value and connector_telemetry_instances to ["a"]; apply, check SSH through Twingate, then set instances to ["a", "b"] and clear this. Without it connector b would lose its telemetry and be replaced in the same plan as a. |
object({status_updates_enabled = optional(bool), log_level = optional(number), connection_logs = optional(bool, false), metrics_port = optional(number), labels = optional(map(string), {})}) |
{} |
none (the bootstrap module validates it, the same checks as connector_telemetry); non-nullable (null means {}) | tfvars (local plans) or Spacelift stack environment (TF_VAR_*) | { log_level = 5 } |
public | exists |
connector_telemetry_instancesWhich connectors get connector_telemetry: a, b or both. Only matters once connector_telemetry is set. Set ["a"] first, apply, check SSH through Twingate to the new connector, then ["a", "b"]: that is the one-at-a-time rollout. A connector not listed gets the default (nothing). |
list(string) |
["a", "b"] |
at least one entry; each entry is a or b, no repeats | tfvars (local plans) or Spacelift stack environment (TF_VAR_*) | ["a"] |
public | exists |
connector_telemetryTwingate connector telemetry for both connectors. Default {} is the live state and changes nothing. status_updates_enabled is a provider setting that is not sent while unset, and replaces nothing. log_level (TWINGATE_LOG_LEVEL), connection_logs (true writes TWINGATE_LOG_ANALYTICS=v3), metrics_port (TWINGATE_METRICS_PORT) and labels (TWINGATE_LABEL_<KEY>) add lines to /etc/twingate/connector.conf: that changes user_data and the plan shows the droplets of every connector in connector_telemetry_instances REPLACED (both by default). Roll out one connector at a time with connector_telemetry_instances, with an SSH-through-Twingate check between (Trace-One-Change-End-to-End, variant C). Opens no firewall port by itself: set metrics_source_tag to admit the metrics port from that one tag. Labels are shown in the Twingate Admin Console: never put a secret in one. |
object({status_updates_enabled = optional(bool), log_level = optional(number), connection_logs = optional(bool, false), metrics_port = optional(number), labels = optional(map(string), {})}) |
{} |
log_level in {3, 4, 5, 7} or unset; metrics_port a whole number 1024 to 65535 and not 9100, or unset; labels: at most 10, keys ^[A-Z][A-Z0-9_]{0,31}$, values ^[A-Za-z0-9._-]{1,64}$ (no spaces, quotes or line breaks, because they are written into the connector config); connector_telemetry is non-nullable (null means {}) | tfvars or Spacelift stack environment (TF_VAR_connector_telemetry as JSON) | { log_level = 5, connection_logs = true, metrics_port = 9999, labels = { DEPLOYED_BY = "opentofu" } } |
public | exists |
metadata_lockLock the cloud metadata endpoint (169.254.169.254, which serves each connector's user-data and so its Twingate tokens) to root and to the do-agent user (DigitalOcean's monitoring agent reads it; allowed only when that user exists). Default false is the live state: merging code with this unset plans no change. true adds an nftables table, a script and a systemd unit through user_data, which REPLACES the droplet of every connector in metadata_lock_instances (both by default). Roll out one connector at a time: true with metadata_lock_instances ["a"], apply, check SSH through Twingate, then ["a", "b"]. Not yet tested on a real machine. |
bool |
false |
none; non-nullable (null means false) | tfvars (local plans) or Spacelift stack environment (TF_VAR_*) | true |
public | exists |
metadata_lock_instancesWhich connectors get metadata_lock: a, b or both. Only matters once metadata_lock is true. Set ["a"] first, apply, check SSH through Twingate to the new connector, then ["a", "b"]. A connector not listed runs without the lock, as it does today. |
list(string) |
["a", "b"] |
at least one entry; each entry is a or b, no repeats | tfvars (local plans) or Spacelift stack environment (TF_VAR_*) | ["a"] |
public | exists |
metrics_enabledInstall node exporter and expose connector metrics. |
bool |
false |
none | tfvars (local plans) or Spacelift stack environment (TF_VAR_*) | true |
public | planned |
node_exporter_portNode exporter port. |
number |
9100 |
1024 to 65535 | tfvars (local plans) or Spacelift stack environment (TF_VAR_*) | 9100 |
public | planned |
metrics_port_connectorConnector metrics port. |
number |
required | 1024 to 65535; differs from node_exporter_port | tfvars (local plans) or Spacelift stack environment (TF_VAR_*) | 9101 |
public | planned |
wazuh_agent_groupWazuh agent group for the cloud hosts. |
string |
"cloud" |
^[a-zA-Z0-9_.-]+$ | tfvars (local plans) or Spacelift stack environment (TF_VAR_*) | cloud-site-tor1 |
public | planned |
labelsTelemetry labels. |
map(string) |
{} |
keys limited to env, role, owner_team; values at most 32 characters | tfvars (local plans) or Spacelift stack environment (TF_VAR_*) | {env = "lab"} |
public | planned |
| Setting | Type | Default | Validation | Set where | Example | Sensitivity | Status |
|---|---|---|---|---|---|---|---|
regionAWS region name. The price table was recorded for us-east-1; another region needs its own check. |
string |
"us-east-1" |
^[a-z]{2}(-[a-z]+)+-[0-9]$ (for example us-east-1) | tfvars (local plans) or Spacelift stack environment (TF_VAR_*) | us-east-1 |
public | exists |
vpc_ip_rangePrivate CIDR. Must not overlap any homelab range (confirmed in the stage guide before apply). |
string |
"10.78.0.0/24" |
none in the stack; modules/aws/vpc checks it is a private IPv4 network address, /16 to /28 | tfvars (local plans) or Spacelift stack environment (TF_VAR_*) | 10.99.0.0/24 |
private (not a credential, but identifies the owner) | exists |
availability_zoneAvailability zone of the one subnet. Null lets AWS choose. |
string |
null |
none | tfvars (local plans) or Spacelift stack environment (TF_VAR_*) | us-east-1a |
public | exists |
project_nameValue of the Project tag that every AWS resource of the stack receives. |
string |
"clouds-against-humanity" |
none | tfvars (local plans) or Spacelift stack environment (TF_VAR_*) | clouds-against-humanity |
public | exists |
| Setting | Type | Default | Validation | Set where | Example | Sensitivity | Status |
|---|---|---|---|---|---|---|---|
host_firewallA default-drop host firewall (nftables table inet cah_host, input chain only) on each connector, a second layer behind the cloud firewall. Only loopback, established traffic, the ICMP and DHCP the network card needs, TCP 22 from host_firewall_ssh_sources and, when connector_telemetry.metrics_port is set, that port from the same sources are let in. Default false is the live state: merging code with this unset plans no change. true adds a script, a systemd unit and the nftables package through user_data, which REPLACES the instance of every connector in host_firewall_instances (both by default). The script checks the ruleset, applies it, verifies sshd still listens and removes the table if not. Roll out one connector at a time: true with host_firewall_instances ["a"], apply, check SSH through Twingate, then ["a", "b"]. Not yet tested on a real machine. |
bool |
false |
none; non-nullable (null means false) | tfvars (local plans) or Spacelift stack environment (TF_VAR_*) | true |
public | exists |
host_firewall_instancesWhich connectors get host_firewall: a, b or both. Only matters once host_firewall is true. Set ["a"] first, apply, check SSH through Twingate to the new connector, then ["a", "b"]. A connector not listed runs without the host firewall, as it does today. |
list(string) |
["a", "b"] |
at least one entry; each entry is a or b, no repeats | tfvars (local plans) or Spacelift stack environment (TF_VAR_*) | ["a"] |
public | exists |
host_firewall_ssh_sourcesIPv4 CIDRs allowed to reach TCP 22 (and the connector metrics port) through the host firewall. Default the three private RFC 1918 blocks: the cloud firewall already limits SSH to the connector tag and this only narrows it to private space, because the module does not know the VPC CIDR. Narrow it to your VPC range if you know it. A /0 is refused on purpose: it would defeat the narrowing, and the cloud firewall stays the outer layer. Only matters once host_firewall is true. |
list(string) |
["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"] |
at least one IPv4 CIDR, no repeats; no leading zeros in octets or prefix; prefix 1 to 32 (/0 is refused on purpose); host bits and overlaps allowed | tfvars (local plans) or Spacelift stack environment (TF_VAR_*) | ["10.0.0.0/8"] |
public | exists |
regionAWS region name, the same value as stack 00. |
string |
required | ^[a-z]{2}(-[a-z]+)+-[0-9]$ (for example us-east-1) | Spacelift stack dependency, mapped to a TF_VAR_* environment variable (output of stack 00) | us-east-1 |
public | exists |
vpc_idID of the VPC created by stack 00. |
string |
required | ^vpc-[0-9a-f]+$ | Spacelift stack dependency, mapped to a TF_VAR_* environment variable (output of stack 00) | vpc-00000000000000000 |
public | exists |
subnet_idID of the public subnet created by stack 00. |
string |
required | ^subnet-[0-9a-f]+$ | Spacelift stack dependency, mapped to a TF_VAR_* environment variable (output of stack 00) | subnet-00000000000000000 |
public | exists |
project_nameValue of the Project tag that every AWS resource of the stack receives. Same as stack 00. |
string |
"clouds-against-humanity" |
none | tfvars (local plans) or Spacelift stack environment (TF_VAR_*) | clouds-against-humanity |
public | exists |
twingate_networkBare Twingate network subdomain (the tenant name). |
string |
required | ^[a-z0-9-]+$ (no URL, no quotes) | Doppler (config prd), exported as TF_VAR_* by the Spacelift hook (TWINGATE_NETWORK) | example-tenant |
private (not a credential, but identifies the owner) | exists |
admin_ssh_public_keyPublic SSH key for the cah-admin user on the connectors (modules/twingate-bootstrap requires type and key with no comment). No AWS key pair is created. |
string |
required | none in the stack (the bootstrap module requires '<ssh-ed25519|ssh-rsa> <key>' with no comment) | Doppler (config prd), exported as TF_VAR_* by the Spacelift hook (ADMIN_SSH_PUBLIC_KEY) | ssh-ed25519 AAAAEXAMPLEONLYNOTAREALKEY |
private (not a credential, but identifies the owner) | exists |
admin_user_idsTwingate user ids placed in the admin group. |
list(string) |
required | at least one entry | Doppler (config prd), exported as TF_VAR_* by the Spacelift hook (ADMIN_USER_IDS, a JSON list) | ["example-user-id"] |
private (not a credential, but identifies the owner) | exists |
timezoneIANA time zone of the machines, also the zone reboot_time(s) and the weekly connector update are read in. Replaces a literal in the cloud-init template; the default renders byte-identical user_data. |
string |
"America/New_York" |
^(UTC|[A-Za-z_]+(/[A-Za-z0-9_+-]+){1,2})$ (an IANA-shaped name; line breaks, spaces and YAML-special characters are rejected) and the name must be listed in modules/twingate-bootstrap/timezones.txt (generated by scripts/gen_timezones.py; enforced in the bootstrap module, which every stack goes through). Changing it changes user_data, which is force-new and replaces the droplets | tfvars (local plans) or Spacelift stack environment (TF_VAR_*) | America/New_York |
public | exists |
connector_telemetry_previousWhat the connectors NOT listed in connector_telemetry_instances keep: the telemetry they run today. Default {} is right for the first activation. For a later change (say log_level 5 to 7) set connector_telemetry to the new value, this to the old value and connector_telemetry_instances to ["a"]; apply, check SSH through Twingate, then set instances to ["a", "b"] and clear this. Without it connector b would lose its telemetry and be replaced in the same plan as a. |
object({status_updates_enabled = optional(bool), log_level = optional(number), connection_logs = optional(bool, false), metrics_port = optional(number), labels = optional(map(string), {})}) |
{} |
none (the bootstrap module validates it, the same checks as connector_telemetry); non-nullable (null means {}) | tfvars (local plans) or Spacelift stack environment (TF_VAR_*) | { log_level = 5 } |
public | exists |
connector_telemetry_instancesWhich connectors get connector_telemetry: a, b or both. Only matters once connector_telemetry is set. Set ["a"] first, apply, check SSH through Twingate to the new connector, then ["a", "b"]: that is the one-at-a-time rollout. A connector not listed gets the default (nothing). |
list(string) |
["a", "b"] |
at least one entry; each entry is a or b, no repeats | tfvars (local plans) or Spacelift stack environment (TF_VAR_*) | ["a"] |
public | exists |
connector_telemetryTwingate connector telemetry for both connectors. Default {} is the live state and changes nothing. status_updates_enabled is a provider setting that is not sent while unset, and replaces nothing. log_level (TWINGATE_LOG_LEVEL), connection_logs (true writes TWINGATE_LOG_ANALYTICS=v3), metrics_port (TWINGATE_METRICS_PORT) and labels (TWINGATE_LABEL_<KEY>) add lines to /etc/twingate/connector.conf: that changes user_data and the plan shows the instances of every connector in connector_telemetry_instances REPLACED (both by default). Roll out one connector at a time with connector_telemetry_instances, with an SSH-through-Twingate check between (Trace-One-Change-End-to-End, variant C). Opens no security group port by itself: set metrics_source_security_group_id to admit the metrics port from that one security group. Labels are shown in the Twingate Admin Console: never put a secret in one. |
object({status_updates_enabled = optional(bool), log_level = optional(number), connection_logs = optional(bool, false), metrics_port = optional(number), labels = optional(map(string), {})}) |
{} |
log_level in {3, 4, 5, 7} or unset; metrics_port a whole number 1024 to 65535 and not 9100, or unset; labels: at most 10, keys ^[A-Z][A-Z0-9_]{0,31}$, values ^[A-Za-z0-9._-]{1,64}$ (no spaces, quotes or line breaks, because they are written into the connector config); connector_telemetry is non-nullable (null means {}) | tfvars or Spacelift stack environment (TF_VAR_connector_telemetry as JSON) | { log_level = 5, connection_logs = true, metrics_port = 9999, labels = { DEPLOYED_BY = "opentofu" } } |
public | exists |
metadata_lockLock the cloud metadata endpoint (169.254.169.254, which serves each connector's user-data and so its Twingate tokens) to root and to the do-agent user (DigitalOcean's monitoring agent reads it; allowed only when that user exists). Default false is the live state: merging code with this unset plans no change. true adds an nftables table, a script and a systemd unit through user_data, which REPLACES the instance of every connector in metadata_lock_instances (both by default). Roll out one connector at a time: true with metadata_lock_instances ["a"], apply, check SSH through Twingate, then ["a", "b"]. Not yet tested on a real machine. |
bool |
false |
none; non-nullable (null means false) | tfvars (local plans) or Spacelift stack environment (TF_VAR_*) | true |
public | exists |
metadata_lock_instancesWhich connectors get metadata_lock: a, b or both. Only matters once metadata_lock is true. Set ["a"] first, apply, check SSH through Twingate to the new connector, then ["a", "b"]. A connector not listed runs without the lock, as it does today. |
list(string) |
["a", "b"] |
at least one entry; each entry is a or b, no repeats | tfvars (local plans) or Spacelift stack environment (TF_VAR_*) | ["a"] |
public | exists |
node_exporterOpt-in node exporter (prometheus-node-exporter from the distro archive) on each connector, bound to its private IPv4 address only. Default {} (enabled false) is the live state: merging code with this unset plans no change (user_data is byte-identical). enabled = true adds a package, a script and a systemd drop-in through user_data, which REPLACES the instance of every connector in node_exporter_instances (both by default; user_data_replace_on_change), and with it the instance's public address. port defaults to 9100. Opens no security group port by itself: set metrics_source_security_group_id as well. Roll out one connector at a time, checking SSH through Twingate. NOT applied and NOT tested on a real machine or account yet; Spacelift's AWS integration is still unchecked and no Spacelift stack exists for this code. |
object({enabled = optional(bool, false), port = optional(number, 9100)}) |
{} |
port a whole number from 1024 to 65535; non-nullable (null means {}) | tfvars (local plans) or Spacelift stack environment (TF_VAR_*) | { enabled = true, port = 9100 } |
public | exists |
node_exporter_instancesWhich connectors get node_exporter: a, b or both. Only matters once node_exporter.enabled is true. Set ["a"] first, apply, check SSH through Twingate to the new connector, then ["a", "b"]. A connector not listed runs without the exporter, as it does today. |
list(string) |
["a", "b"] |
at least one entry; each entry is a or b, no repeats | tfvars (local plans) or Spacelift stack environment (TF_VAR_*) | ["a"] |
public | exists |
metrics_source_security_group_idSecurity group id of the machines that may scrape the connectors' metrics ports (the node exporter port and connector_telemetry.metrics_port, tcp), supplied by the operator. Empty (default) is the live state: no metrics rule exists. When set, the connector security group gains one inbound rule per enabled metrics port whose source is that group (referenced_security_group_id): never an address, an IPv6 range or a prefix list. The group must already exist in the same VPC. Changes the security group only; replaces no instance. Must differ from the connector security group itself (refused otherwise: it would admit connector-to-connector metrics). To change the source group, apply once with this empty, then set the new group: a different group is a different rule key. |
string |
"" |
empty, or ^sg-[0-9a-f]{8,17}$ (a security group id: an address or CIDR is refused); must not equal the connector security group (a precondition on the rule and on the stack output) | tfvars (local plans) or Spacelift stack environment (TF_VAR_*) | sg-0123456789abcdef0 (illustrative) |
public | exists |
| Setting | Type | Default | Validation | Set where | Example | Sensitivity | Status |
|---|---|---|---|---|---|---|---|
regionDigitalOcean region slug of the site this stack serves, from the foundation stack's region output. Names are built from resource_key, which defaults to this. |
string |
required | ^[a-z]{3}[0-9]$ (three lowercase letters and a digit) | Spacelift stack environment (foundation output mapped to TF_VAR_region) or tfvars | tor1 |
public | exists |
resource_keyKey used in the names of the Twingate objects this stack creates (telemetry-<key>-logging); empty means region. IMMUTABLE for the life of a stack: changing it renames, and so replaces, every object. Never derived from site_name. |
string |
"" |
empty, or ^[a-z][a-z0-9-]{1,23}$; immutable on a live stack | tfvars (local plans) or Spacelift stack environment (TF_VAR_*) | tor1 |
public | exists |
site_nameTelemetry label only (the site field of scrape_targets and site_identity); empty means site-<region>. In no resource name or tag; changing it replaces nothing. |
string |
"" |
empty, or ^[a-z][a-z0-9-]{1,23}$; unique across sites (a rule for the person, one stack cannot test it) | tfvars (local plans) or Spacelift stack environment (TF_VAR_*) | site-tor1 |
public | exists |
logging_enabledCreate the logging Twingate service and one resource per logging_servers element. False creates nothing. Verify the Twingate plan allows the services first (UNVERIFIED). |
bool |
false |
none | tfvars (local plans) or Spacelift stack environment (TF_VAR_*) | true |
public | exists |
metrics_enabledCreate the scrape Twingate service and one resource per scrape_allowlist entry per connector. False creates nothing. |
bool |
false |
none | tfvars (local plans) or Spacelift stack environment (TF_VAR_*) | true |
public | exists |
logging_serversHome logging endpoints reached only through Twingate; one Twingate resource per element, named from name and resource_key, never from the address. |
list(object({name=string,address=string,port=number,protocol=string,purpose=string})) |
[] |
name unique and ^[a-z][a-z0-9-]{0,40}$ (so never an address); address an RFC 1918 or RFC 5737 IPv4 literal or a host name with no scheme (a public IPv4 literal is refused); port 1-65535; protocol tcp or udp; purpose one of wazuh-agent, wazuh-enroll, syslog, metrics-remote-write | Per-site Doppler config as JSON (private, non-secret), exported as TF_VAR_logging_servers | [{name="siem-home-agent", address="192.0.2.10", port=1514, protocol="tcp", purpose="wazuh-agent"}] |
private (not a credential, but identifies the owner) | exists |
home_remote_network_idId of the Twingate remote network that holds the home logging connector, where the logging resources are created. Required when logging_enabled (a precondition). |
string |
"" |
none (a precondition on the logging service checks it when logging_enabled) | Per-site Doppler config or Spacelift stack environment (TF_VAR_home_remote_network_id) | example-home-remote-network-id |
private (not a credential, but identifies the owner) | exists |
cloud_remote_network_idId of this site's cloud Twingate remote network (access-<resource_key>, made by stack 10), where the scrape resources are created. Required when metrics_enabled (a precondition). Stack 10 does not output it today. |
string |
"" |
none (a precondition on the scrape service checks it when metrics_enabled) | Spacelift stack environment (TF_VAR_cloud_remote_network_id) | example-cloud-remote-network-id |
private (not a credential, but identifies the owner) | exists |
node_exporter_portTCP port the node exporter listens on, on each connector. Keep it equal to node_exporter.port in the access stack (stack 10), which installs the exporter (opt-in) and opens the port from metrics_source_tag; the stacks are separate, so the two values are kept in step by hand. |
number |
9100 |
whole number from 1024 to 65535 | tfvars (local plans) or Spacelift stack environment (TF_VAR_*) | 9100 |
public | exists |
metrics_port_connectorTCP port of the Twingate connector's own metrics endpoint. Keep it equal to connector_telemetry.metrics_port in the access stack (stack 10). No default: the connector's metrics bind address is UNVERIFIED. Required when metrics_enabled (a precondition). |
number |
null |
unset, or a whole number from 1024 to 65535; when metrics_enabled it must be set, differ from node_exporter_port and not be 9100 (kept for the node exporter, as in the access stack) | tfvars (local plans) or Spacelift stack environment (TF_VAR_*) | 9101 (illustrative) |
public | exists |
scrape_allowlistWhich connector endpoints become Twingate resources for scraping, per connector. Required non-empty when metrics_enabled. |
list(object({name=string,host_role=string,port=number})) |
[] |
name unique and ^[a-z][a-z0-9-]{0,30}$; host_role connector; port 1024-65535; when metrics_enabled each port must be node_exporter_port or metrics_port_connector | tfvars (local plans) or Spacelift stack environment (TF_VAR_*) | [{name="node", host_role="connector", port=9100}] |
public | exists |
connector_private_ipsPrivate addresses of the site's connectors, keyed a and b: the access stack's connector_private_ips output. Required when metrics_enabled. Used for the scrape resources and the sensitive scrape_targets output only. |
map(string) |
{} |
keys only a and b; values private IPv4 addresses (10/8, 172.16/12, 192.168/16) | Spacelift stack environment (TF_VAR_connector_private_ips, a JSON map) or by hand; a map through a stack dependency is UNVERIFIED | {a="10.99.0.5", b="10.99.0.6"} |
private (not a credential, but identifies the owner) | exists |
wazuh_agent_groupWazuh agent group for this site's cloud hosts (telemetry label; read by the host bootstrap in a later step). |
string |
"cloud" |
^[a-zA-Z0-9_.-]+$ | tfvars (local plans) or Spacelift stack environment (TF_VAR_*) | cloud-site-tor1 |
public | exists |
labelsExtra telemetry labels, kept few to bound metric cardinality. |
map(string) |
{} |
keys limited to env, role, owner_team; values at most 32 characters | tfvars (local plans) or Spacelift stack environment (TF_VAR_*) | {env = "lab"} |
public | exists |
| Setting | Type | Default | Validation | Set where | Example | Sensitivity | Status |
|---|---|---|---|---|---|---|---|
grafana_enabledCreate the optional Grafana host. |
bool |
false |
none | tfvars (local plans) or Spacelift stack environment (TF_VAR_*) | false |
public | planned |
grafana_droplet_sizeDroplet size for the Grafana host; belongs in cost.json like the connectors. |
string |
"s-1vcpu-1gb" |
present in docs/cost-prices.json | cost.json | s-1vcpu-1gb |
public | planned |
metrics_retention_daysDays of metrics kept by the cloud local store. |
number |
15 |
1 to 400 | tfvars (local plans) or Spacelift stack environment (TF_VAR_*) | 15 |
public | planned |
log_retention_daysDays of logs kept by the cloud local store. |
number |
30 |
1 to 400 | tfvars (local plans) or Spacelift stack environment (TF_VAR_*) | 30 |
public | planned |
soc_admin_user_idsTwingate user ids for the SOC tools. |
list(string) |
required | at least one when grafana_enabled | Doppler (config prd), exported as TF_VAR_* by the Spacelift hook | ["example-user-id"] |
private (not a credential, but identifies the owner) | planned |
| Setting | Type | Default | Validation | Set where | Example | Sensitivity | Status |
|---|---|---|---|---|---|---|---|
layerStack layer, used in names (for example foundation). |
string |
required | none | set by the calling stack (not a user setting) | foundation |
public | exists |
regionDigitalOcean region slug. |
string |
required | none | set by the calling stack (not a user setting) | tor1 |
public | exists |
ip_rangePrivate IPv4 CIDR for the VPC. |
string |
required | valid CIDR in 10/8, 172.16/12 or 192.168/16 | set by the calling stack (not a user setting) | 10.99.0.0/24 |
private (not a credential, but identifies the owner) | exists |
resource_keyKey used in resource names in place of region; empty means region. Set by the calling stack from its resource_key. |
string |
"" |
empty, or ^[a-z][a-z0-9-]{1,23}$ | set by the calling stack (not a user setting) | tor1 |
public | exists |
| Setting | Type | Default | Validation | Set where | Example | Sensitivity | Status |
|---|---|---|---|---|---|---|---|
layerStack layer, used in names (the module has no description for it). |
string |
required | none | set by the calling stack (not a user setting) | access |
public | exists |
regionRegion, used in names (the module has no description for it). |
string |
required | none | set by the calling stack (not a user setting) | tor1 |
public | exists |
purposeRole in the firewall name (for example baseline or connector). |
string |
required | none | set by the calling stack (not a user setting) | connector |
public | exists |
tagDroplet tag this firewall attaches to. |
string |
required | none | set by the calling stack (not a user setting) | cah-connector-tor1 |
public | exists |
extra_outbound_rulesOutbound rules appended to the defaults. |
list(object({protocol=string,port_range=string})) |
[] |
none | set by the calling stack (not a user setting) | [{protocol="tcp", port_range="30000-31000"}] |
public | exists |
inbound_from_tag_rulesInbound rules that admit only droplets carrying this firewall's own tag. Empty means deny-all inbound. |
list(object({protocol=string,port_range=string})) |
[] |
protocol tcp or udp and a non-empty port_range | set by the calling stack (not a user setting) | [{protocol="tcp", port_range="22"}] |
public | exists |
outbound_to_tag_rulesOutbound rules that reach only droplets carrying this firewall's own tag (no addresses). Empty means none. |
list(object({protocol=string,port_range=string})) |
[] |
protocol tcp or udp and a non-empty port_range | set by the calling stack (not a user setting) | [{protocol="tcp", port_range="22"}] |
public | exists |
inbound_from_other_tagsInbound rules that admit only droplets carrying the given other tag (source_tags, never an address). Additive. Empty means none. |
list(object({protocol=string,port_range=string,tag=string})) |
[] |
protocol tcp or udp; port_range a port or ascending range within 1..65535; tag 1 to 255 characters of letters, digits, colon, hyphen, underscore; no duplicate (protocol, port_range, tag) | set by the calling stack (not a user setting) | [{protocol="tcp", port_range="9100", tag="cah-connector-tor1"}] |
public | exists |
outbound_to_other_tagsOutbound rules that reach only droplets carrying the given other tag (destination_tags, never an address). Additive. Empty means none. |
list(object({protocol=string,port_range=string,tag=string})) |
[] |
protocol tcp or udp; port_range a port or ascending range within 1..65535; tag 1 to 255 characters of letters, digits, colon, hyphen, underscore; no duplicate (protocol, port_range, tag) | set by the calling stack (not a user setting) | [{protocol="tcp", port_range="9100", tag="cah-connector-tor1"}] |
public | exists |
resource_keyKey used in resource names in place of region; empty means region. Set by the calling stack from its resource_key. |
string |
"" |
empty, or ^[a-z][a-z0-9-]{1,23}$ | set by the calling stack (not a user setting) | tor1 |
public | exists |
| Setting | Type | Default | Validation | Set where | Example | Sensitivity | Status |
|---|---|---|---|---|---|---|---|
host_firewallA default-drop host firewall (nftables, input only). Default false changes nothing: user_data is byte-identical (golden hashes in the tests). true adds a script, a systemd unit and the nftables package to user_data, which replaces the machine. |
bool |
false |
none; non-nullable (null means false) | the calling module | true |
public | exists |
host_firewall_ssh_sourcesIPv4 CIDRs allowed to reach TCP 22 (and the metrics port) when host_firewall is true. Default the three private RFC 1918 blocks. |
list(string) |
["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"] |
at least one IPv4 CIDR, no repeats; no leading zeros in octets or prefix; prefix 1 to 32 (/0 is refused on purpose); host bits and overlaps allowed | the calling module | ["10.0.0.0/8"] |
public | exists |
layerStack layer, used in names (the module has no description for it). |
string |
required | none | set by the calling stack (not a user setting) | access |
public | exists |
regionRegion, used in names (the module has no description for it). |
string |
required | none | set by the calling stack (not a user setting) | tor1 |
public | exists |
remote_network_idTwingate remote network (site) shared by every connector of the stack. |
string |
required | none | set by the calling stack (not a user setting) | example-remote-network-id |
public | exists |
instanceOne lowercase letter or digit that tells this connector from its siblings (a, b). |
string |
required | ^[a-z0-9]$ | set by the calling stack (not a user setting) | a |
public | exists |
twingate_networkBare Twingate network subdomain. |
string |
required | ^[a-z0-9-]+$ | set by the calling stack (not a user setting) | example-tenant |
private (not a credential, but identifies the owner) | exists |
admin_ssh_public_keyPublic key for the cah-admin user; an optional trailing comment is dropped before cloud-init. |
string |
required | ^(ssh-ed25519|ssh-rsa|ecdsa-sha2-nistp(256|384|521)) [A-Za-z0-9+/]+=*( [A-Za-z0-9@._+:=/-]{1,64})?$ | set by the calling stack (not a user setting) | ssh-ed25519 AAAAEXAMPLEONLYNOTAREALKEY |
private (not a credential, but identifies the owner) | exists |
reboot_timeHH:MM (in timezone, the droplet's local time) when unattended-upgrades may reboot this droplet. |
string |
required | ^([01][0-9]|2[0-3]):[0-5][0-9]$ | set by the calling stack (not a user setting) | 04:00 |
public | exists |
timezoneIANA time zone of the machines, also the zone reboot_time(s) and the weekly connector update are read in. Replaces a literal in the cloud-init template; the default renders byte-identical user_data. |
string |
"America/New_York" |
^(UTC|[A-Za-z_]+(/[A-Za-z0-9_+-]+){1,2})$ (an IANA-shaped name; line breaks, spaces and YAML-special characters are rejected) and the name must be listed in modules/twingate-bootstrap/timezones.txt (generated by scripts/gen_timezones.py; enforced in the bootstrap module, which every stack goes through). Changing it changes user_data, which is force-new and replaces the droplets | set by the calling stack (not a user setting) | America/New_York |
public | exists |
connector_telemetryConnector telemetry. Default {} adds no line to the connector config: user_data is byte-identical (golden hashes in the tests). status_updates_enabled is provider-side; the other fields render lines in /etc/twingate/connector.conf and change user_data. Labels are shown in the Twingate Admin Console: never put a secret in one. |
object({status_updates_enabled = optional(bool), log_level = optional(number), connection_logs = optional(bool, false), metrics_port = optional(number), labels = optional(map(string), {})}) |
{} |
log_level in {3, 4, 5, 7} or unset; metrics_port a whole number 1024 to 65535 and not 9100, or unset; labels: at most 10, keys ^[A-Z][A-Z0-9_]{0,31}$, values ^[A-Za-z0-9._-]{1,64}$ (no spaces, quotes or line breaks, because they are written into the connector config); connector_telemetry is non-nullable (null means {}) | the calling module | { log_level = 5, connection_logs = true, metrics_port = 9999, labels = { DEPLOYED_BY = "opentofu" } } |
public | exists |
node_exporterOpt-in node exporter: installs prometheus-node-exporter from the distro archive and binds it to the machine's private IPv4 address only (a boot-time script picks the first RFC 1918 address and refuses to start the exporter if there is none). Default {} (enabled false): user_data is byte-identical (golden hashes in the tests). enabled = true changes user_data and REPLACES the machine. With host_firewall the port is allowed from host_firewall_ssh_sources. NOT applied and NOT tested on a real machine yet. |
object({enabled = optional(bool, false), port = optional(number, 9100)}) |
{} |
port a whole number from 1024 to 65535 (checked even while disabled); non-nullable (null means {}); must differ from connector_telemetry.metrics_port (a precondition) | the calling module | { enabled = true, port = 9100 } |
public | exists |
log_shippingOpt-in log shipping (Wazuh agent and auditd, enrolled at first boot with a secret pulled from Doppler). Default {} (enabled false): user_data is byte-identical (golden hashes in the tests). enabled = true changes user_data and REPLACES the machine. Required when enabled: manager_address, agent_name (<site>-connector-<instance>, ending in this machine's instance) and apt_key_fingerprint (40 uppercase hex digits, no default on purpose). No secret is an input here. NOT applied and NOT tested on a real machine; the 1 GB droplet's memory headroom is UNMEASURED. |
object({enabled = optional(bool, false), manager_address = optional(string, ""), manager_port = optional(number, 1514), enroll_port = optional(number, 1515), agent_name = optional(string, ""), agent_group = optional(string, ""), apt_key_fingerprint = optional(string, ""), agent_version = optional(string, "")}) |
{} |
ports whole numbers 1-65535 (checked even while disabled); manager_address, agent_name (^[a-z][a-z0-9-]{0,55}-connector-[a-z0-9]$) and apt_key_fingerprint (^[0-9A-F]{40}$) required when enabled; agent_group and agent_version format checks; agent_name must end in -connector-<instance> (a precondition) | the calling module | { enabled = true, manager_address = "siem.example.com", agent_name = "site-tor1-connector-a", apt_key_fingerprint = "<40 uppercase hex characters>" } |
private (not a credential, but identifies the owner) | exists |
log_shipping_bootstrap_tokenThe one secret-shaped value that reaches user_data when log_shipping is enabled: a short-lived, read-only Doppler token for the hostboot config. Written to a root-only file that the first-boot script deletes before using it. Required when log_shipping.enabled; never rendered otherwise. |
string |
"" |
empty or 16-512 characters of letters, digits, dot, underscore, hyphen; required when log_shipping.enabled (a precondition) | the calling module | "" |
bootstrap credential (a short-lived bearer token rendered into user_data for first boot) | exists |
metadata_lockLock the metadata endpoint (169.254.169.254) to root and the do-agent user. Default false changes nothing: user_data is byte-identical (golden hashes in the tests). true adds an nftables table, a script, a systemd unit and the nftables package to user_data, which replaces the machine. |
bool |
false |
none; non-nullable (null means false) | the calling module | true |
public | exists |
resource_keyKey used in resource names in place of region; empty means region. Set by the calling stack from its resource_key. |
string |
"" |
empty, or ^[a-z][a-z0-9-]{1,23}$ | set by the calling stack (not a user setting) | tor1 |
public | exists |
| Setting | Type | Default | Validation | Set where | Example | Sensitivity | Status |
|---|---|---|---|---|---|---|---|
host_firewallA default-drop host firewall (nftables, input only). Default false changes nothing: user_data is byte-identical (golden hashes in the tests). true adds a script, a systemd unit and the nftables package to user_data, which replaces the droplet. |
bool |
false |
none; non-nullable (null means false) | the calling stack | true |
public | exists |
host_firewall_ssh_sourcesIPv4 CIDRs allowed to reach TCP 22 (and the metrics port) when host_firewall is true. Default the three private RFC 1918 blocks. |
list(string) |
["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"] |
none here; the bootstrap module requires IPv4 CIDRs without leading zeros, prefix 1 to 32 (/0 refused), no repeats | the calling stack | ["10.0.0.0/8"] |
public | exists |
layerStack layer, used in names (the module has no description for it). |
string |
required | none | set by the calling stack (not a user setting) | access |
public | exists |
regionRegion, used in names (the module has no description for it). |
string |
required | none | set by the calling stack (not a user setting) | tor1 |
public | exists |
vpc_idVPC the droplet joins (the module has no description for it). |
string |
required | none | set by the calling stack (not a user setting) | 00000000-0000-4000-8000-000000000000 |
public | exists |
tagsDroplet tags, including the baseline firewall tag. Set at creation. |
list(string) |
required | none | set by the calling stack (not a user setting) | ["cah-baseline-tor1", "cah-connector-tor1"] |
public | exists |
sizeDroplet size slug. The stack reads it from cost.json. |
string |
required | none in the module (cost_check.py checks the price table) | set by the calling stack (not a user setting) | s-1vcpu-1gb |
public | exists |
remote_network_idTwingate remote network (site) shared by every connector of the stack. |
string |
required | none | set by the calling stack (not a user setting) | example-remote-network-id |
public | exists |
ssh_key_fingerprintFingerprint of the stack's admin SSH key. |
string |
required | none | set by the calling stack (not a user setting) | 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00 |
public | exists |
instanceOne lowercase letter or digit that tells this connector from its siblings (a, b). |
string |
required | none in this module (the bootstrap module checks it) | set by the calling stack (not a user setting) | a |
public | exists |
twingate_networkBare Twingate network subdomain. |
string |
required | none in this module (the bootstrap module checks it) | set by the calling stack (not a user setting) | example-tenant |
private (not a credential, but identifies the owner) | exists |
admin_ssh_public_keyPublic key for the cah-admin user. |
string |
required | none in this module (the bootstrap module checks it) | set by the calling stack (not a user setting) | ssh-ed25519 AAAAEXAMPLEONLYNOTAREALKEY |
private (not a credential, but identifies the owner) | exists |
reboot_timeHH:MM (America/New_York) when unattended-upgrades may reboot. |
string |
required | none in this module (the bootstrap module checks it) | set by the calling stack (not a user setting) | 04:00 |
public | exists |
timezoneIANA time zone of the machines, also the zone reboot_time(s) and the weekly connector update are read in. Replaces a literal in the cloud-init template; the default renders byte-identical user_data. |
string |
"America/New_York" |
^(UTC|[A-Za-z_]+(/[A-Za-z0-9_+-]+){1,2})$ (an IANA-shaped name; line breaks, spaces and YAML-special characters are rejected) and the name must be listed in modules/twingate-bootstrap/timezones.txt (generated by scripts/gen_timezones.py; enforced in the bootstrap module, which every stack goes through). Changing it changes user_data, which is force-new and replaces the droplets | set by the calling stack (not a user setting) | America/New_York |
public | exists |
connector_telemetryConnector telemetry, passed to the bootstrap module (which validates it). Default {} changes nothing. log_level, connection_logs, metrics_port and labels change user_data and replace the droplet; status_updates_enabled does not. Labels are shown in the Twingate Admin Console: never put a secret in one. |
object({status_updates_enabled = optional(bool), log_level = optional(number), connection_logs = optional(bool, false), metrics_port = optional(number), labels = optional(map(string), {})}) |
{} |
none (validated in modules/twingate-bootstrap); connector_telemetry is non-nullable (null means {}) | the calling stack | { log_level = 5, connection_logs = true, metrics_port = 9999, labels = { DEPLOYED_BY = "opentofu" } } |
public | exists |
node_exporterOpt-in node exporter bound to the private address, passed to the bootstrap module, which validates it. Default {} changes nothing. enabled = true changes user_data and replaces the droplet. |
object({enabled = optional(bool, false), port = optional(number, 9100)}) |
{} |
none (validated in modules/twingate-bootstrap); node_exporter is non-nullable (null means {}) | the calling stack | { enabled = true, port = 9100 } |
public | exists |
log_shippingOpt-in Wazuh agent and auditd with a first-boot enrollment, passed to the bootstrap module, which validates it. Default {} changes nothing. enabled = true changes user_data and replaces the droplet. |
object({enabled = optional(bool, false), manager_address = optional(string, ""), manager_port = optional(number, 1514), enroll_port = optional(number, 1515), agent_name = optional(string, ""), agent_group = optional(string, ""), apt_key_fingerprint = optional(string, ""), agent_version = optional(string, "")}) |
{} |
none (validated in modules/twingate-bootstrap); non-nullable (null means {}) | the calling stack | { enabled = true, manager_address = "siem.example.com", agent_name = "site-tor1-connector-a", apt_key_fingerprint = "<40 uppercase hex characters>" } |
private (not a credential, but identifies the owner) | exists |
log_shipping_bootstrap_tokenShort-lived, read-only Doppler token for the hostboot config, passed to the bootstrap module. Required when log_shipping.enabled; never rendered otherwise. |
string |
"" |
none (validated in modules/twingate-bootstrap) | the calling stack | "" |
bootstrap credential (a short-lived bearer token rendered into user_data for first boot) | exists |
metadata_lockLock the metadata endpoint (169.254.169.254) to root and the do-agent user. Default false changes nothing: user_data is byte-identical (golden hashes in the tests). true adds an nftables table, a script, a systemd unit and the nftables package to user_data, which replaces the droplet. |
bool |
false |
none; non-nullable (null means false) | the calling stack | true |
public | exists |
resource_keyKey used in resource names in place of region; empty means region. Set by the calling stack from its resource_key. |
string |
"" |
empty, or ^[a-z][a-z0-9-]{1,23}$ | set by the calling stack (not a user setting) | tor1 |
public | exists |
| Setting | Type | Default | Validation | Set where | Example | Sensitivity | Status |
|---|---|---|---|---|---|---|---|
layerStack layer, used in names (for example foundation). |
string |
required | none | set by the calling stack (not a user setting) | access |
public | exists |
regionAWS region name (for example us-east-1). Used in names; the stack's provider block decides where the VPC is really created. |
string |
required | none | set by the calling stack (not a user setting) | us-east-1 |
public | exists |
ip_rangePrivate IPv4 CIDR for the VPC. The one public subnet uses the whole range. |
string |
required | see the validation block in variables.tf | set by the calling stack (not a user setting) | 10.99.0.0/24 |
private (not a credential, but identifies the owner) | exists |
availability_zoneAvailability zone of the subnet (for example us-east-1a). Null lets AWS choose. AWS extension: a subnet lives in exactly one zone. |
string |
null |
none | set by the calling stack (not a user setting) | us-east-1a |
public | exists |
| Setting | Type | Default | Validation | Set where | Example | Sensitivity | Status |
|---|---|---|---|---|---|---|---|
layerStack layer, used in names (for example access). |
string |
required | none | set by the calling stack (not a user setting) | access |
public | exists |
regionAWS region name (for example us-east-1). Used in names. |
string |
required | none | set by the calling stack (not a user setting) | us-east-1 |
public | exists |
purposeRole in the group name (for example connector). |
string |
required | none | set by the calling stack (not a user setting) | connector |
public | exists |
vpc_idVPC the security group belongs to. From the vpc module's id. |
string |
required | none | set by the calling stack (not a user setting) | vpc-00000000000000000 |
public | exists |
extra_outbound_rulesOutbound rules to any IPv4 address, appended to the five defaults. port_range is a port (443), a range (30000-31000) or "all" (ports 0-65535 of that one protocol). |
list(object({protocol=string,port_range=string})) |
[] |
see the validation block in variables.tf | set by the calling stack (not a user setting) | [{ protocol = "tcp", port_range = "30000-31000" }] |
public | exists |
inbound_from_member_rulesInbound rules that admit only members of this security group itself (a rule whose source is the group). Empty means no inbound rule at all. |
list(object({protocol=string,port_range=string})) |
[] |
see the validation block in variables.tf | set by the calling stack (not a user setting) | [{ protocol = "tcp", port_range = "22" }] |
public | exists |
inbound_from_other_groupsInbound rules that admit only members of the given OTHER security group (one rule per entry, source = that group, never an address, IPv6 range or prefix list). Additive to inbound_from_member_rules. Empty means none. A source equal to this module's own group is refused (use inbound_from_member_rules). |
list(object({protocol=string,port_range=string,security_group_id=string})) |
[] |
see the validation blocks in variables.tf (tcp or udp, ports 1 to 65535, sg-... id, no repeats); a source equal to the module's own group is refused by a precondition on the rule | set by the calling stack (not a user setting) | [{ protocol = "tcp", port_range = "9100", security_group_id = "sg-0123456789abcdef0" }] |
public | exists |
outbound_to_member_rulesOutbound rules that reach only members of this security group itself (a rule whose destination is the group, no address). Empty means none. |
list(object({protocol=string,port_range=string})) |
[] |
see the validation block in variables.tf | set by the calling stack (not a user setting) | [{ protocol = "tcp", port_range = "22" }] |
public | exists |
| Setting | Type | Default | Validation | Set where | Example | Sensitivity | Status |
|---|---|---|---|---|---|---|---|
host_firewallA default-drop host firewall (nftables, input only). Default false changes nothing: user_data is byte-identical (golden hashes in the tests). true adds a script, a systemd unit and the nftables package to user_data, which replaces the instance. |
bool |
false |
none; non-nullable (null means false) | the calling stack | true |
public | exists |
host_firewall_ssh_sourcesIPv4 CIDRs allowed to reach TCP 22 (and the metrics port) when host_firewall is true. Default the three private RFC 1918 blocks. |
list(string) |
["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"] |
none here; the bootstrap module requires IPv4 CIDRs without leading zeros, prefix 1 to 32 (/0 refused), no repeats | the calling stack | ["10.0.0.0/8"] |
public | exists |
layerlayer (the module has no description for it). |
string |
required | none | set by the calling stack (not a user setting) | access |
public | exists |
regionregion (the module has no description for it). |
string |
required | none | set by the calling stack (not a user setting) | us-east-1 |
public | exists |
subnet_idPublic subnet the instance launches into. From the vpc module's subnet_id. |
string |
required | none | set by the calling stack (not a user setting) | subnet-00000000000000000 |
public | exists |
security_group_idThe one security group attached to the instance's network interface at launch, so there is no unprotected moment. From the firewall module's id (its single rule target). A single string, not a list: security group rules are additive, so any second group a caller added could open inbound access to the public address. |
string |
required | see the validation block in variables.tf | set by the calling stack (not a user setting) | sg-00000000000000000 |
public | exists |
instance_typeEC2 instance type. The stack reads it from cost.json (local.<x>.type) so the price and the plan can never disagree. |
string |
required | see the validation block in variables.tf | set by the calling stack (not a user setting) | t4g.micro |
public | exists |
architectureCPU architecture of the Ubuntu image: arm64 (Graviton types such as t4g) or amd64 (types such as t3). Must match instance_type. |
string |
"arm64" |
see the validation block in variables.tf | set by the calling stack (not a user setting) | arm64 |
public | exists |
root_volume_sizeRoot disk size in GiB (gp3). This number is an input to the cost gate: the AMI's own default is not priced, so the size is always set here. 8 GiB is the smallest the Ubuntu image accepts. |
number |
8 |
see the validation block in variables.tf | set by the calling stack (not a user setting) | 8 |
public | exists |
remote_network_idTwingate remote network (site) shared by every connector of the stack. |
string |
required | none | set by the calling stack (not a user setting) | example-remote-network-id |
public | exists |
instanceOne lowercase letter or digit that tells this connector from its siblings (a, b). |
string |
required | none in this module (the bootstrap module checks it) | set by the calling stack (not a user setting) | a |
public | exists |
twingate_networkBare Twingate network subdomain (acme for acme.twingate.com). Sensitive because the tenant slug identifies the tenant. |
string |
required | none in this module (the bootstrap module checks it) | set by the calling stack (not a user setting) | example-tenant |
private (not a credential, but identifies the owner) | exists |
admin_ssh_public_keyPublic key (type and key, no comment) for the cah-admin user. Installed by cloud-init; no AWS key pair is used. |
string |
required | none in this module (the bootstrap module checks it) | set by the calling stack (not a user setting) | ssh-ed25519 AAAAEXAMPLEONLYNOTAREALKEY |
private (not a credential, but identifies the owner) | exists |
reboot_timeHH:MM (in timezone, the instance's local time) when unattended-upgrades may reboot this instance. Give each connector a different time so they never reboot together. |
string |
required | none in this module (the bootstrap module checks it) | set by the calling stack (not a user setting) | 04:00 |
public | exists |
timezoneIANA time zone of the machines, also the zone reboot_time(s) and the weekly connector update are read in. Replaces a literal in the cloud-init template; the default renders byte-identical user_data. |
string |
"America/New_York" |
^(UTC|[A-Za-z_]+(/[A-Za-z0-9_+-]+){1,2})$ (an IANA-shaped name; line breaks, spaces and YAML-special characters are rejected) and the name must be listed in modules/twingate-bootstrap/timezones.txt (generated by scripts/gen_timezones.py; enforced in the bootstrap module, which every stack goes through). Changing it changes user_data, which is force-new and replaces the droplets | set by the calling stack (not a user setting) | America/New_York |
public | exists |
connector_telemetryConnector telemetry, passed to the bootstrap module (which validates it). Default {} changes nothing. log_level, connection_logs, metrics_port and labels change user_data and replace the instance; status_updates_enabled does not. Labels are shown in the Twingate Admin Console: never put a secret in one. |
object({status_updates_enabled = optional(bool), log_level = optional(number), connection_logs = optional(bool, false), metrics_port = optional(number), labels = optional(map(string), {})}) |
{} |
none (validated in modules/twingate-bootstrap); connector_telemetry is non-nullable (null means {}) | the calling stack | { log_level = 5, connection_logs = true, metrics_port = 9999, labels = { DEPLOYED_BY = "opentofu" } } |
public | exists |
metadata_lockLock the metadata endpoint (169.254.169.254) to root and the do-agent user. Default false changes nothing: user_data is byte-identical (golden hashes in the tests). true adds an nftables table, a script, a systemd unit and the nftables package to user_data, which replaces the instance. |
bool |
false |
none; non-nullable (null means false) | the calling stack | true |
public | exists |
node_exporterOpt-in node exporter bound to the private address, passed to the bootstrap module, which validates it. Default {} changes nothing (user_data is byte-identical, golden hashes in the tests). enabled = true changes user_data and replaces the instance. |
object({enabled = optional(bool, false), port = optional(number, 9100)}) |
{} |
none (validated in modules/twingate-bootstrap); node_exporter is non-nullable (null means {}) | the calling stack | { enabled = true, port = 9100 } |
public | exists |
These are not OpenTofu variables. The cost gate (scripts/cost_check.py) reads them, and stack 10 reads its droplet sizes from cost.json, so there is one source of truth for money. To change a size you edit cost.json and docs/stages/approved.json together; that is the lesson.
| Setting | File | Pointer | Current value | Validation | Example | Status |
|---|---|---|---|---|---|---|
| connector-a size | stacks/10-access/cost.json |
/resources/0/size |
"s-1vcpu-1gb" |
price must exist in docs/cost-prices.json; both connectors are kept equal by a test | s-1vcpu-1gb |
exists |
| connector-b size | stacks/10-access/cost.json |
/resources/1/size |
"s-1vcpu-1gb" |
price must exist in docs/cost-prices.json | s-1vcpu-1gb |
exists |
| connector-a backups | stacks/10-access/cost.json |
/resources/0/backups |
false |
false or true; true adds 20% of the droplet price | false |
exists |
| connector-b backups | stacks/10-access/cost.json |
/resources/1/backups |
false |
false or true | false |
exists |
| stage 0 resources | stacks/00-foundation/cost.json |
/resources |
[] |
list of costed resources; empty because stage 0 costs nothing | [] |
exists |
| approved monthly total (DigitalOcean) | docs/stages/approved.json |
/providers/digitalocean/approved_monthly_total |
12.0 |
the computed total may not exceed it; the sum across providers may not exceed the ceiling | 12.0 |
exists |
| monthly ceiling (current phase) | docs/stages/approved.json |
/phases/stage-1/ceiling |
30.0 |
dollars; one ceiling per phase in phases, the one named by current_phase is enforced; a later phase may raise it, lowering needs lowers: true, and hard_max (required, with hard_max_reason) caps every phase | 30.0 |
exists |
| droplet price table entry | docs/cost-prices.json |
/droplets/s-1vcpu-1gb |
6 |
dollars per month for a size; a size absent from the table fails the gate | 6 |
exists |
| AWS connector-a instance type | stacks/aws/10-access/cost.json |
/resources/0/type |
"t4g.micro" |
price must exist in docs/cost-prices/aws.json; read by the stack as local.connector_a.type; both connectors are kept equal by a test | t4g.micro |
exists |
| AWS connector-b instance type | stacks/aws/10-access/cost.json |
/resources/1/type |
"t4g.micro" |
price must exist in docs/cost-prices/aws.json; read by the stack as local.connector_b.type; both connectors are kept equal by a test | t4g.micro |
exists |
| approved monthly total (AWS) | docs/stages/approved.json |
/providers/aws/approved_monthly_total |
21.0 |
the computed AWS total may not exceed it; counted against the ceiling only while the AWS entry is active | 21.0 |
exists |
These are literals in the code, not variables. They are listed so the table is complete and so you know a change is a code edit, not a value.
| Setting | Where | Current value | Validation | Plan | Status |
|---|---|---|---|---|---|
| connector extra outbound rules | stacks/10-access/main.tf |
tcp 30000-31000 and udp 1-65535 |
none | kept hard-coded on purpose (low value to expose) | exists |
| connector SSH rule between connectors | stacks/10-access/main.tf |
tcp 22, tag to tag |
firewall module validation | kept hard-coded on purpose | exists |
| Twingate remote network location | stacks/10-access/main.tf |
OTHER |
Twingate provider enum | not planned | exists |
| project environment | stacks/00-foundation/main.tf |
Production |
DigitalOcean enum | not planned | exists |
This table lists names and where they live. It never holds a value. See Doppler and secrets for how they reach a run.
| Name | Class | Lives in | Used by | Delivered as | Expiry | Rotation | Status |
|---|---|---|---|---|---|---|---|
DIGITALOCEAN_TOKEN |
secret | Doppler config prd | stacks/00-foundation, stacks/10-access | DIGITALOCEAN_TOKEN | 90 days | docs/spacelift-secrets.md | exists |
TWINGATE_API_TOKEN |
secret | Doppler config prd | stacks/10-access, stacks/20-telemetry | TWINGATE_API_TOKEN | 90 days | docs/spacelift-secrets.md | exists |
TWINGATE_NETWORK |
private | Doppler config prd | stacks/10-access, stacks/20-telemetry | TWINGATE_NETWORK and TF_VAR_twingate_network | none | not a credential | exists |
ADMIN_SSH_PUBLIC_KEY |
private | Doppler config prd | stacks/10-access | TF_VAR_admin_ssh_public_key | none | replace the key and re-apply | exists |
ADMIN_SSH_KEY_NAME |
private | Doppler config prd | stacks/10-access | TF_VAR_admin_ssh_key_name | none | not a credential | exists |
ADMIN_USER_IDS |
private | Doppler config prd | stacks/10-access | TF_VAR_admin_user_ids | none | not a credential | exists |
DOPPLER_TOKEN |
secret | Spacelift context cah-doppler (write-only) | the Spacelift hook script | DOPPLER_TOKEN | 90 days | docs/spacelift-secrets.md | exists |
DOPPLER_PROJECT |
private | Spacelift context cah-doppler | the Spacelift hook script | DOPPLER_PROJECT | none | not a credential | exists |
DOPPLER_CONFIG |
private | Spacelift context cah-doppler | the Spacelift hook script | DOPPLER_CONFIG | none | not a credential | exists |
Per-host Twingate service keys |
secret | Doppler hostboot config (one per site) | each host, pulled at first boot | first-boot pull, never a variable | 365 days by default | issue a new key, revoke the old one | planned |
Doppler hostboot bootstrap token |
secret | Doppler service token for the config cah-<resource_key>-hostboot, created out of band; handed to the stack as TF_VAR_log_shipping_bootstrap_token | stacks/10-access (user_data of each connector with log_shipping.enabled) | sensitive stack variable log_shipping_bootstrap_token; the first-boot script deletes the token file | UNVERIFIED: must expire on its own within a day; revoke after the first boot if Doppler's minimum is longer | create a new token for each rebuild; revoke the old one | planned |
Wazuh enrollment password |
secret | Doppler hostboot config (one per site) | each connector with log_shipping.enabled, pulled at first boot as WAZUH_ENROLLMENT_PASSWORD | first-boot pull, never a variable | rotated after each enrollment window | rotate on the manager | planned |
Grafana admin password |
secret | Doppler hostboot config (one per site) | optional NOC host | first-boot pull, never a variable | none | rotate in Doppler and on the host | planned |
Nothing here is checked by a machine except that a pointer file exists. Each has an owner and a place to look.
| Setting | Owner | Where | Pointer | Status |
|---|---|---|---|---|
| Spacelift stack environment (foundation outputs mapped to TF_VAR_*) | maintainer | Spacelift UI, stack dependencies of cah-10-access | docs/stages/01-access.md | exists |
| Doppler project and config prd | maintainer | Doppler console | docs/spacelift-secrets.md | exists |
| Twingate console (remote network, connectors, users) | maintainer | Twingate admin console | docs/wiki/Twingate.md | exists |
| DigitalOcean account (sizes available to the account, SSH key registration) | maintainer | DigitalOcean console and API | docs/cost.md | exists |
| Home Prometheus scrape interval | home monitoring repository | that repository's target file | specified in the stage 2 design, not built here | planned |
| Home firewall rules for the logging connector | maintainer | the home firewall | specified in the stage 2 design, not built here | planned |
Next: Variables, tfvars and secrets, step 5 of the build track on the Reading order page, which says which of these places a value belongs in and why.
Source for these pages lives in docs/wiki/ and is published by the Wiki workflow on every merge to main. Corrections are welcome as pull requests; edits made in the wiki editor are overwritten on the next publish. Apache-2.0 licensed, see LICENSE and NOTICE in the repository.
Support this project: support.chiefgyk3d.com, or see Home.
Core path: read in this order
- Start here if you are new
- What is the cloud
- Networking basics
- Identity, secrets, zero trust
- Infrastructure as code
- Git and pull requests
- Containers and Docker (optional)
- How money works
- Enterprise habits
- Architecture overview
- Why ZTNA and not a VPN
- Twingate
- DigitalOcean network and firewalls
- Droplet hardening
- OpenTofu, stacks and modules
- Spacelift
- Doppler and secrets
- CI, tests and the cost gate
- Threat model
- Lessons learned
- Roadmap
Track: build it yourself
Track: understand the design
The rest of this track is on the core path; see Reading order.
Track: security and threat modelling
- Threat model exercise
- Threat Dragon exercise
- Threat frameworks: STRIDE, ATT&CK, ATLAS
- Troubleshooting index
The Threat model is core step 19.
Track: detection and monitoring
- A small example: the enterprise landscape
- Detection, Wazuh and the landscape
- Cloud telemetry: what and why (unverified draft)
- Multiple sites and identity (unverified draft)
- Telemetry crosses the trust boundary (unverified draft)
- Reading a dashboard as an operator (unverified draft)
- Build your own mini SOC (unverified draft)
- Stage 2 home-side runbook (unverified draft)
- Grafana dashboards (unverified draft)