chore(netbird): switch provider (#238)

* chore(netbird): change provider, normalize

* commit
This commit is contained in:
Antoine Lecompte
2026-06-30 10:09:42 -04:00
committed by GitHub
parent 8821fad205
commit 2ec0e668ad
18 changed files with 204 additions and 183 deletions
+36 -24
View File
@@ -416,14 +416,14 @@ carry different policies.
| `deployment/prod/global/netbird` | prod, **account-wide** (cross-region) | `yucca/prod/global/netbird/…` |
| `deployment/prod/htz-fsn1/netbird` | prod, **region** htz-fsn1 | `yucca/prod/htz-fsn1/netbird/…` |
Staging is single-layer. **Prod is layered**: a `global` layer (account-wide
groups + policies) above per-region layers. The global layer owns the
**`YUCCA_RESOURCE`** tag group and the account-wide **`yucca → yucca_resource`**
policy (see below). Region groups are region-scoped (`YUCCA_PROD_<REGION>_<ROLE>`)
so a network router's peers are unambiguously *that region's* mgmt nodes. A
region layer consumes a global group via a terragrunt `dependency` on
`prod/global` → the module's `external_groups` input (htz-fsn1 does this for
`yucca_resource`). The root terragrunt derives `stack` from the full sub-path,
Staging is single-layer. **Prod is layered**: a `global` layer (reserved for
account-wide / cross-region groups + policies) above per-region layers. The
global layer is empty today — each region owns its own resource group and the
`yucca → resources` policy is module-generated per layer (see below). Region
groups are region-scoped (`YUCCA_PROD_<REGION>_<ROLE>`) so a network router's
peers are unambiguously *that region's* mgmt nodes. A region layer can still
consume a global group via a terragrunt `dependency` on `prod/global` → the
module's `external_groups` input, but none do today. The root terragrunt derives `stack` from the full sub-path,
so `prod/htz-fsn1/netbird` gets its own state key. (Now that the fabric stack
lives in its own `prod/htz-fsn1/fabric/` sub-stack, the region root carries no
terragrunt.hcl, so `prod/htz-fsn1/netbird` uses a normal
@@ -432,22 +432,31 @@ gone.) Rendered NetBird object names and 1P item titles are unchanged by the
rename (`YUCCA_STAGING_*`, `NETBIRD_YUCCA_PROD_HTZ_FSN1_*`): the `env`→`partition`
/ `site`→`region` swap keeps the same string values.
### The `yucca` / `yucca_resource` access model
### The `yucca` → resource-groups access model
Two groups drive account-wide access to routed subnets (rendered names in caps):
Users reach routed subnets through two kinds of group (rendered names in caps):
- **`yucca`** — the existing **users** group (people). External (looked up by its
actual name `yucca`); never managed here.
- **`YUCCA_RESOURCE`** — the shared **resource tag**, managed in `prod/global`
(logical key `yucca_resource`). Every routed `netbird_network_resource` (across
sites) is tagged into it.
- **resource groups** — any group flagged **`resource = true`** in a layer's
`groups`. Each layer owns its own (e.g. htz-fsn1's `resources` →
`YUCCA_PROD_HTZ_FSN1_RESOURCES`); every routed `netbird_network_resource` is
tagged into one.
One global policy in `prod/global` — `yucca → yucca_resource`, `bidirectional =
false` — lets users reach every tagged resource. Because `yucca_resource` is only
ever a policy *destination*, the tagged resources can't reach each other (or call
back to users). Site layers don't reference `yucca` at all; they just tag their
resources into `yucca_resource` (pulled from `prod/global` via the dependency),
so the link is the shared tag, not a cross-stack group reference.
For each layer that owns ≥1 resource group, the `netbird-env` module
**auto-generates** a `<PREFIX>_YUCCA_TO_RESOURCES` policy (`bidirectional =
false`) whose destinations are *all* of that layer's resource groups — so adding
a resource group automatically grants `yucca` users access to it, with no policy
to edit. The source is `var.yucca_users_group` (default `yucca`, looked up by
name; set null to opt a layer out). Because resource groups are only ever policy
*destinations*, the tagged resources can't reach each other (or call back to
users). The union of these per-layer policies is the account-wide "yucca reaches
every resource group we create" guarantee.
> The former single shared **`yucca_resource`** tag in `prod/global` (one
> account-wide `yucca → yucca_resource` policy, consumed by sites via a terragrunt
> dependency) was **retired** in favour of this per-layer model — no cross-stack
> group reference, and the destination list is derived, not hand-maintained.
Staging additionally grants its `ci` group access to the existing **Liberty
Park** infra groups (where the staging nodes live today) — those are external
@@ -459,7 +468,8 @@ Groups, setup keys, policies and networks reference groups by **logical key**,
never opaque NetBird IDs:
```hcl
groups = { ci = {}, mgmt = {}, talos = {}, k8s_operator = {} }
groups = { ci = {}, mgmt = {}, talos = {}, k8s_operator = {},
resources = { resource = true } } # resource group → auto yucca→resources policy
setup_keys = {
ci = { type = "reusable", ephemeral = true, auto_groups = ["ci"] }
@@ -477,7 +487,9 @@ policies = {
```
NetBird is **default-deny** — a peer gets only the access its groups' policies
grant; an empty `policies` map means total isolation.
grant; an empty `policies` map means total isolation. The `yucca →` resource
policy is *not* declared here: the module generates it from every group flagged
`resource = true` (see the access model above).
### Networks (prod htz-fsn1) — CIDRs propagated, not hardcoded
@@ -486,9 +498,9 @@ The htz-fsn1 site layer exposes a NetBird **Network** named `HTZ-FSN1`: the
`netbird_network_resource`. The **CIDRs are derived from the same
`fabric-addressing` module the fabric stack uses** (re-instantiated in the
layer's `addressing.tf` — a pure, stateless module, so no duplication and no
cross-stack coupling). Every resource is tagged into `yucca_resource`, so access
is the one global `yucca → yucca_resource` policy. The only per-site input is the
site id (the CIDRs flow from it):
cross-stack coupling). Every resource is tagged into the site's own `resources`
group, so access is the module-generated `YUCCA_PROD_HTZ_FSN1_YUCCA_TO_RESOURCES`
policy. The only per-site input is the site id (the CIDRs flow from it):
```hcl
site_id = 40 # mirrors prod/htz-fsn1; feeds fabric-addressing → the routed CIDRs
@@ -24,25 +24,27 @@ provider "registry.opentofu.org/1password/onepassword" {
]
}
provider "registry.opentofu.org/netbirdio/netbird" {
version = "0.0.9"
constraints = "~> 0.0.9"
provider "registry.terraform.io/futo-org/netbird" {
version = "1.0.1"
constraints = "~> 1.0"
hashes = [
"h1:H8GG0MZqAAXqnrMCw0Q3vcE3gGFEqYmDF9YY2RnD18Y=",
"zh:1f34fba3ecfe0efa36d4b4bddf5714c69124e705d1e371e65abd291887d43385",
"zh:203f671af4d1f5376f4e20fb8d82cc8dc6c4fd136d9abffe8eb7b7f34e27197b",
"zh:21cb344302bdbadbc2779768116c7351d915bb7678a4847e9e2c8623d0032c48",
"zh:2368868e0f86b458f83b6598317ff5fde0d4079500d4567e99f46bf80c63f07a",
"zh:3237a426818eb3906153b1cf7fb6dfe9d52128972e7cab17c324d88788f86863",
"zh:427f8e8ba190d69cfd493b1598b4bf7940fe9a521a333df3540d2e0ea07543cf",
"zh:689cb219f3936500f5a3147299961f8740e505612b5182d93840cb8152d89b4f",
"zh:6e594e69d9e107c45ed4677a9bff68de2dd4232900b636327b24c819bee72c9e",
"zh:715634d6d0b052ac3f34aeb4c2df42960cdf44dd5c45d060867db78716aebb48",
"h1:/7jw47nsJ3huM4DjtEwAd486NDpOg+YEkfBfLh1ZPGU=",
"h1:FGhKWXOOHG+3K5i2LIFh3vjoeAV+AngSwwTPSHwMskQ=",
"h1:GAirg4Iu5grhEXBFrN8BGCuDN0jy1TyIjL6uBIHT9Rs=",
"h1:o6ixZi6QxtL7o3SkChsiINUXHkHiaJEYII2dnM81f08=",
"zh:10c5908178a89532eb0c8213f9b4e614accd95bdd863ecaada181bd3dcfb2fc4",
"zh:142301b60f38044d341474cddc7a3fe4b21896103783d80d17b9d3ae8bbaf358",
"zh:1703d7e0829af459927237ce7154fd9c161aed062bf3a5cc4f43676727be3222",
"zh:1b90a5a14b48e0905f3877114b8c9225459a6a851d572da511d1432f451837be",
"zh:40fb5e32e53e492625dd2421d550c53edc8d241e2f48886c8b20ffbe1e4e14f8",
"zh:40ff60d0c259a0af774081700ecab581964d0594500d02970f66a3fc6db4a0ad",
"zh:69fb842ad58908d3ae794c8d0aa2fbdfbae0b50f428a9ca009f1be2e59fb545c",
"zh:6ca76c6ed6fbcd159c3f9f80a2ae99592bc5f2c155873fdfda18727f8467849d",
"zh:890df766e9b839623b1f0437355032a3c006226a6c200cd911e15ee1a9014e9f",
"zh:8cbbdde5a0b59f0bb427b403da8993b215d1a6231e0d6b9b4faa89db76b10705",
"zh:af92281c3e14c6af53fb93cee584c5118c5c7bee0acb938b5a367a219d9bc2e2",
"zh:bcdeef5fadf092e33228f28f013af7d8d9553b9d2fb2cecd1894351d5ad37a7a",
"zh:d6c31cb12f18b25c9663c14e737554e06144d4e270e607337bac7963ca3b9542",
"zh:e5873e8e0b29c8cbfd98f04759579da3c0529c8b38608c0f1ea92eb566c8ddc1",
"zh:8f68847b66e7e5e71b80c83aec81cdd68790acb9de17e42ca299c396c2c9fc18",
"zh:9112042e68282e9c1698f3192bbd818d8f09f09932534590ce444f2aeee43d42",
"zh:9bbc34da2991c7504ea75d114ad168f9babe33d61b25dc614bc8a3432bd3b152",
"zh:df00d3aa18251282c471ff56390d530b58b7ae5547ee1e11a3da81e5c47975ca",
"zh:e0668eb65d8b6921e8ae2479cfab2c901198a18eb039c1fa20324301c8303f48",
]
}
@@ -7,36 +7,15 @@
# stack exists as the layer those site layers build on (a cross-site policy, or a
# shared group consumed via the site layer's `external_groups`, would land here).
# The shared resource tag. Site layers tag every routed network resource into
# this group (via a terragrunt dependency on this stack), so one account-wide
# policy governs access to all of them. Explicit name → the unprefixed shared tag
# (without it the module would render "YUCCA_PROD_YUCCA_RESOURCE").
# NOTE: name kept lowercase (verbatim, not UPPER_SNAKE). This group carries the
# site network resources tagged into it, and the NetBird provider can't update a
# group that has resources — so it must keep the exact name it was created with.
groups = {
yucca_resource = { name = "yucca_resource" }
}
# No cross-site groups today. The former shared "yucca_resource" tag was retired
# (#yucca_resource deprecation): each site layer now owns its OWN resource
# group(s) flagged `resource = true`, and the shared netbird-env module
# auto-generates that site's "<PREFIX>_YUCCA_TO_RESOURCES" policy from them — so
# yucca users reach every resource group we create without an account-wide tag or
# a hand-maintained destination list. An account-spanning group would still land
# here (created in this layer, consumed by sites via `external_groups`).
groups = {}
setup_keys = {}
policies = {
# Account-wide: members of the existing "yucca" users group reach every NetBird
# resource tagged into "yucca_resource". One global policy covers all such
# resources across every env/site. `yucca` is an external group resolved by name
# in netbird.tf; `yucca_resource` is the group created above.
#
# bidirectional = false → only yucca users INITIATE to resources. yucca_resource
# is never a source, so the tagged resources can't reach each other (or back to
# users) — just be reached.
yucca-to-resources = {
description = "yucca users → all yucca_resource-tagged resources (account-wide)."
rules = [{
name = "yucca-to-resources"
protocol = "all"
bidirectional = false
sources = ["yucca"]
destinations = ["yucca_resource"]
}]
}
}
policies = {}
+5 -16
View File
@@ -1,8 +1,9 @@
# ─── Global prod NetBird layer ───────────────────────────────────────────────
# Account-wide groups, cross-site policies, and operator setup keys shared by
# every prod site. Site layers (prod/<site>/netbird) build on this — they pull
# this stack's `group_ids` output via a terragrunt dependency and grant the
# global `admins` group access to their site-local servers.
# Reserved for ACCOUNT-WIDE / cross-site prod NetBird objects (groups or policies
# that span every prod site). Empty today — with per-site resource groups and the
# module-generated yucca→resources policy (see netbird-env), all current prod
# objects live in the site layers (prod/<site>/netbird). A cross-site group would
# be created here and consumed by site layers via their `external_groups` input.
#
# One NetBird Cloud account backs all envs; objects here are namespaced
# "yucca-prod-*". Auth (both injected by `op run --env-file=tf/.env.prod`):
@@ -11,14 +12,6 @@
provider "netbird" {}
provider "onepassword" {}
# The existing account-wide "yucca" users group. Account-global access policies
# reference it by the logical key `yucca` (handed to the module as an external
# group). Any NetBird resource tagged into this group (see the site layers'
# network resources) is then reachable by yucca users via the yucca→yucca policy.
data "netbird_group" "yucca" {
name = "yucca"
}
module "netbird" {
source = "../../../../shared/modules/netbird-env"
@@ -29,10 +22,6 @@ module "netbird" {
groups = var.groups
setup_keys = var.setup_keys
policies = var.policies
external_groups = {
yucca = data.netbird_group.yucca.id
}
}
output "group_ids" {
@@ -53,7 +53,8 @@ variable "domain" {
variable "groups" {
type = map(object({
name = optional(string)
name = optional(string)
resource = optional(bool, false)
}))
default = {}
}
@@ -2,8 +2,8 @@ terraform {
required_version = "~> 1.11"
required_providers {
netbird = {
source = "netbirdio/netbird"
version = "~> 0.0.9"
source = "registry.terraform.io/futo-org/netbird"
version = "~> 1.0"
}
onepassword = {
source = "1Password/onepassword"
+20 -18
View File
@@ -24,25 +24,27 @@ provider "registry.opentofu.org/1password/onepassword" {
]
}
provider "registry.opentofu.org/netbirdio/netbird" {
version = "0.0.9"
constraints = "~> 0.0.9"
provider "registry.terraform.io/futo-org/netbird" {
version = "1.0.1"
constraints = "~> 1.0"
hashes = [
"h1:H8GG0MZqAAXqnrMCw0Q3vcE3gGFEqYmDF9YY2RnD18Y=",
"zh:1f34fba3ecfe0efa36d4b4bddf5714c69124e705d1e371e65abd291887d43385",
"zh:203f671af4d1f5376f4e20fb8d82cc8dc6c4fd136d9abffe8eb7b7f34e27197b",
"zh:21cb344302bdbadbc2779768116c7351d915bb7678a4847e9e2c8623d0032c48",
"zh:2368868e0f86b458f83b6598317ff5fde0d4079500d4567e99f46bf80c63f07a",
"zh:3237a426818eb3906153b1cf7fb6dfe9d52128972e7cab17c324d88788f86863",
"zh:427f8e8ba190d69cfd493b1598b4bf7940fe9a521a333df3540d2e0ea07543cf",
"zh:689cb219f3936500f5a3147299961f8740e505612b5182d93840cb8152d89b4f",
"zh:6e594e69d9e107c45ed4677a9bff68de2dd4232900b636327b24c819bee72c9e",
"zh:715634d6d0b052ac3f34aeb4c2df42960cdf44dd5c45d060867db78716aebb48",
"h1:/7jw47nsJ3huM4DjtEwAd486NDpOg+YEkfBfLh1ZPGU=",
"h1:FGhKWXOOHG+3K5i2LIFh3vjoeAV+AngSwwTPSHwMskQ=",
"h1:GAirg4Iu5grhEXBFrN8BGCuDN0jy1TyIjL6uBIHT9Rs=",
"h1:o6ixZi6QxtL7o3SkChsiINUXHkHiaJEYII2dnM81f08=",
"zh:10c5908178a89532eb0c8213f9b4e614accd95bdd863ecaada181bd3dcfb2fc4",
"zh:142301b60f38044d341474cddc7a3fe4b21896103783d80d17b9d3ae8bbaf358",
"zh:1703d7e0829af459927237ce7154fd9c161aed062bf3a5cc4f43676727be3222",
"zh:1b90a5a14b48e0905f3877114b8c9225459a6a851d572da511d1432f451837be",
"zh:40fb5e32e53e492625dd2421d550c53edc8d241e2f48886c8b20ffbe1e4e14f8",
"zh:40ff60d0c259a0af774081700ecab581964d0594500d02970f66a3fc6db4a0ad",
"zh:69fb842ad58908d3ae794c8d0aa2fbdfbae0b50f428a9ca009f1be2e59fb545c",
"zh:6ca76c6ed6fbcd159c3f9f80a2ae99592bc5f2c155873fdfda18727f8467849d",
"zh:890df766e9b839623b1f0437355032a3c006226a6c200cd911e15ee1a9014e9f",
"zh:8cbbdde5a0b59f0bb427b403da8993b215d1a6231e0d6b9b4faa89db76b10705",
"zh:af92281c3e14c6af53fb93cee584c5118c5c7bee0acb938b5a367a219d9bc2e2",
"zh:bcdeef5fadf092e33228f28f013af7d8d9553b9d2fb2cecd1894351d5ad37a7a",
"zh:d6c31cb12f18b25c9663c14e737554e06144d4e270e607337bac7963ca3b9542",
"zh:e5873e8e0b29c8cbfd98f04759579da3c0529c8b38608c0f1ea92eb566c8ddc1",
"zh:8f68847b66e7e5e71b80c83aec81cdd68790acb9de17e42ca299c396c2c9fc18",
"zh:9112042e68282e9c1698f3192bbd818d8f09f09932534590ce444f2aeee43d42",
"zh:9bbc34da2991c7504ea75d114ad168f9babe33d61b25dc614bc8a3432bd3b152",
"zh:df00d3aa18251282c471ff56390d530b58b7ae5547ee1e11a3da81e5c47975ca",
"zh:e0668eb65d8b6921e8ae2479cfab2c901198a18eb039c1fa20324301c8303f48",
]
}
@@ -7,6 +7,13 @@ groups = {
mgmt = {} # management nodes (configured via ansible); also the route peers
talos = {} # Talos cluster nodes → YUCCA_PROD_HTZ_FSN1_TALOS
k8s_operator = {} # in-cluster kubernetes operator → YUCCA_PROD_HTZ_FSN1_K8S_OPERATOR
# Site resource tag → YUCCA_PROD_HTZ_FSN1_RESOURCES. The routed network resources
# (netbird.tf) are tagged into it. `resource = true` makes the netbird-env module
# auto-add it to the generated YUCCA_PROD_HTZ_FSN1_YUCCA_TO_RESOURCES policy, so
# yucca users reach every routed subnet. (Replaces the retired shared
# "yucca_resource" tag — each site now owns its resource group.)
resources = { resource = true }
}
setup_keys = {
@@ -31,16 +38,16 @@ policies = {
# CI also reaches the routed site subnets (switch vme 10.40.5.0/24 + api +
# cluster nets), so the fabric jobs can NETCONF the switches over the overlay
# (the switches are routed resources behind the mgmt peers, not peers
# themselves). yucca_resource is the shared tag on every routed resource,
# resolved from the global layer via external_groups.
# themselves). `resources` is this site's resource group — every routed
# resource is tagged into it (see the groups block + netbird.tf).
ci-to-resources = {
description = "CI → routed site subnets (yucca_resource)."
description = "CI → routed site subnets (resources)."
rules = [{
name = "ci-to-resources"
protocol = "all"
bidirectional = false
sources = ["ci"]
destinations = ["yucca_resource"]
destinations = ["resources"]
}]
}
}
@@ -49,7 +56,7 @@ policies = {
# plan in addressing.tf; the routed-network CIDRs derive from it.
#
# The "HTZ-FSN1" Network (built in netbird.tf) routes the site subnets — CIDRs
# propagated from the fabric-addressing plan — and tags every resource into the
# shared "yucca_resource" group, so access is governed by the account-wide
# yucca→yucca_resource policy (prod/global). Nothing to declare here per subnet.
# propagated from the fabric-addressing plan — and tags every resource into this
# site's "resources" group, so access is governed by the module-generated
# YUCCA_PROD_HTZ_FSN1_YUCCA_TO_RESOURCES policy. Nothing to declare here per subnet.
site_id = 40
@@ -12,10 +12,10 @@ locals {
# Routed subnets for the HTZ-FSN1 Network. The mgmt nodes (router peers) expose
# these to the overlay. ADDRESSES ARE PROPAGATED from the fabric-addressing plan
# (addressing.tf) — not hardcoded — so the cluster definition stays the single
# source of truth. Every resource is tagged into the shared "yucca_resource"
# group (from the global layer, via var.external_groups), so the account-wide
# yucca→yucca_resource policy (prod/global) governs access — and resources
# never appear as a policy source, so they can't reach each other.
# source of truth. Every resource is tagged into this site's own "resources"
# group (flagged `resource = true` in netbird.auto.tfvars), so the module-
# generated yucca→resources policy governs access — and resources never appear
# as a policy source, so they can't reach each other.
routed = {
mgmt = { address = module.addr_site.mgmt_cidr, description = "OOB / vme management network" }
api = { address = module.addr_site.api_cidr, description = "Site-global API network" }
@@ -31,7 +31,7 @@ locals {
for name, r in local.routed : name => {
address = r.address
description = r.description
groups = ["yucca_resource"]
groups = ["resources"]
}
}
}
@@ -49,10 +49,6 @@ module "netbird" {
setup_keys = var.setup_keys
policies = var.policies
networks = local.netbird_networks
# yucca_resource comes from the global layer via the terragrunt dependency
# (see terragrunt.hcl → external_groups input).
external_groups = var.external_groups
}
output "group_ids" {
@@ -12,22 +12,9 @@ include "root" {
#
# netbird.auto.tfvars is loaded automatically; partition/region are injected by
# the root.
# Depends on the global layer for the shared "yucca_resource" tag — this site's
# routed network resources are tagged into it so the account-wide yucca→
# yucca_resource policy (prod/global) governs their access. prod/global must
# apply before this stack; mock_outputs cover validate/plan before that.
dependency "global" {
config_path = "../../global/netbird"
mock_outputs = {
group_ids = { yucca_resource = "mock-yucca-resource-group-id" }
}
mock_outputs_allowed_terraform_commands = ["validate", "plan"]
}
inputs = {
external_groups = {
yucca_resource = dependency.global.outputs.group_ids.yucca_resource
}
}
#
# This site owns its own resource group (the `resources` group in
# netbird.auto.tfvars, flagged `resource = true`): the netbird-env module tags
# the routed subnets into it and auto-generates the site's
# YUCCA_PROD_HTZ_FSN1_YUCCA_TO_RESOURCES policy. No dependency on prod/global —
# the former shared "yucca_resource" tag was retired.
@@ -45,15 +45,10 @@ variable "domain" {
default = null
}
variable "external_groups" {
description = "Groups owned by the global prod layer, injected by the terragrunt dependency (logical key → NetBird group ID). Today: { admins = <global admins id> }."
type = map(string)
default = {}
}
variable "groups" {
type = map(object({
name = optional(string)
name = optional(string)
resource = optional(bool, false)
}))
default = {}
}
@@ -2,8 +2,8 @@ terraform {
required_version = "~> 1.11"
required_providers {
netbird = {
source = "netbirdio/netbird"
version = "~> 0.0.9"
source = "registry.terraform.io/futo-org/netbird"
version = "~> 1.0"
}
onepassword = {
source = "1Password/onepassword"
+20 -18
View File
@@ -24,25 +24,27 @@ provider "registry.opentofu.org/1password/onepassword" {
]
}
provider "registry.opentofu.org/netbirdio/netbird" {
version = "0.0.9"
constraints = "~> 0.0.9"
provider "registry.terraform.io/futo-org/netbird" {
version = "1.0.1"
constraints = "~> 1.0"
hashes = [
"h1:H8GG0MZqAAXqnrMCw0Q3vcE3gGFEqYmDF9YY2RnD18Y=",
"zh:1f34fba3ecfe0efa36d4b4bddf5714c69124e705d1e371e65abd291887d43385",
"zh:203f671af4d1f5376f4e20fb8d82cc8dc6c4fd136d9abffe8eb7b7f34e27197b",
"zh:21cb344302bdbadbc2779768116c7351d915bb7678a4847e9e2c8623d0032c48",
"zh:2368868e0f86b458f83b6598317ff5fde0d4079500d4567e99f46bf80c63f07a",
"zh:3237a426818eb3906153b1cf7fb6dfe9d52128972e7cab17c324d88788f86863",
"zh:427f8e8ba190d69cfd493b1598b4bf7940fe9a521a333df3540d2e0ea07543cf",
"zh:689cb219f3936500f5a3147299961f8740e505612b5182d93840cb8152d89b4f",
"zh:6e594e69d9e107c45ed4677a9bff68de2dd4232900b636327b24c819bee72c9e",
"zh:715634d6d0b052ac3f34aeb4c2df42960cdf44dd5c45d060867db78716aebb48",
"h1:/7jw47nsJ3huM4DjtEwAd486NDpOg+YEkfBfLh1ZPGU=",
"h1:FGhKWXOOHG+3K5i2LIFh3vjoeAV+AngSwwTPSHwMskQ=",
"h1:GAirg4Iu5grhEXBFrN8BGCuDN0jy1TyIjL6uBIHT9Rs=",
"h1:o6ixZi6QxtL7o3SkChsiINUXHkHiaJEYII2dnM81f08=",
"zh:10c5908178a89532eb0c8213f9b4e614accd95bdd863ecaada181bd3dcfb2fc4",
"zh:142301b60f38044d341474cddc7a3fe4b21896103783d80d17b9d3ae8bbaf358",
"zh:1703d7e0829af459927237ce7154fd9c161aed062bf3a5cc4f43676727be3222",
"zh:1b90a5a14b48e0905f3877114b8c9225459a6a851d572da511d1432f451837be",
"zh:40fb5e32e53e492625dd2421d550c53edc8d241e2f48886c8b20ffbe1e4e14f8",
"zh:40ff60d0c259a0af774081700ecab581964d0594500d02970f66a3fc6db4a0ad",
"zh:69fb842ad58908d3ae794c8d0aa2fbdfbae0b50f428a9ca009f1be2e59fb545c",
"zh:6ca76c6ed6fbcd159c3f9f80a2ae99592bc5f2c155873fdfda18727f8467849d",
"zh:890df766e9b839623b1f0437355032a3c006226a6c200cd911e15ee1a9014e9f",
"zh:8cbbdde5a0b59f0bb427b403da8993b215d1a6231e0d6b9b4faa89db76b10705",
"zh:af92281c3e14c6af53fb93cee584c5118c5c7bee0acb938b5a367a219d9bc2e2",
"zh:bcdeef5fadf092e33228f28f013af7d8d9553b9d2fb2cecd1894351d5ad37a7a",
"zh:d6c31cb12f18b25c9663c14e737554e06144d4e270e607337bac7963ca3b9542",
"zh:e5873e8e0b29c8cbfd98f04759579da3c0529c8b38608c0f1ea92eb566c8ddc1",
"zh:8f68847b66e7e5e71b80c83aec81cdd68790acb9de17e42ca299c396c2c9fc18",
"zh:9112042e68282e9c1698f3192bbd818d8f09f09932534590ce444f2aeee43d42",
"zh:9bbc34da2991c7504ea75d114ad168f9babe33d61b25dc614bc8a3432bd3b152",
"zh:df00d3aa18251282c471ff56390d530b58b7ae5547ee1e11a3da81e5c47975ca",
"zh:e0668eb65d8b6921e8ae2479cfab2c901198a18eb039c1fa20324301c8303f48",
]
}
@@ -56,7 +56,8 @@ variable "domain" {
variable "groups" {
type = map(object({
name = optional(string)
name = optional(string)
resource = optional(bool, false)
}))
default = {}
}
@@ -2,8 +2,8 @@ terraform {
required_version = "~> 1.11"
required_providers {
netbird = {
source = "netbirdio/netbird"
version = "~> 0.0.9"
source = "registry.terraform.io/futo-org/netbird"
version = "~> 1.0"
}
onepassword = {
source = "1Password/onepassword"
+43 -5
View File
@@ -9,11 +9,9 @@
locals {
# DERIVED names are normalized to UPPER_SNAKE: uppercased, hyphens → underscores.
# e.g. name_prefix "yucca_prod_htz-fsn1" + key "mgmt" → "YUCCA_PROD_HTZ_FSN1_MGMT".
# An explicit `name` override is taken VERBATIM (not uppercased) — the NetBird
# provider (v0.0.9) can't update a group that has network resources tagged into
# it (it crashes converting the API's resource objects), so a shared tag group
# like "yucca_resource" must keep the exact name it was created with; renaming
# it is impossible in-place. Network display names are likewise verbatim.
# An explicit `name` override is taken VERBATIM (not uppercased), for groups that
# must keep an exact pre-existing name (e.g. one created outside this module).
# Network display names are likewise verbatim.
group_names = {
for k, g in var.groups : k => coalesce(g.name, upper(replace("${var.name_prefix}_${k}", "-", "_")))
}
@@ -39,6 +37,16 @@ locals {
}
}
]...)
# Groups this layer owns that are flagged `resource = true`: the destinations of
# the auto-generated yucca→resources policy. New resource groups are picked up
# here automatically — nothing to wire into a policy by hand.
resource_group_keys = [for k, g in var.groups : k if g.resource]
resource_group_ids = [for k in local.resource_group_keys : netbird_group.this[k].id]
# Manage the yucca→resources policy only when this layer owns ≥1 resource group
# and a users group is configured (var.yucca_users_group != null).
manage_resource_policy = var.yucca_users_group != null && length(local.resource_group_keys) > 0
}
resource "netbird_group" "this" {
@@ -83,6 +91,36 @@ resource "netbird_policy" "this" {
}
}
# ─── yucca users → every resource group (auto-derived) ───────────────────
# Members of the account-wide `yucca` users group reach every group flagged
# `resource = true` in this layer. Destinations are derived from those groups
# (local.resource_group_ids), so any new resource group is covered without
# touching a policy. bidirectional = false → yucca users only INITIATE; the
# resource groups are never a source, so resources can't reach back or each
# other. The users group is looked up by name (it lives outside this stack).
data "netbird_group" "yucca_users" {
count = local.manage_resource_policy ? 1 : 0
name = var.yucca_users_group
}
resource "netbird_policy" "yucca_to_resources" {
count = local.manage_resource_policy ? 1 : 0
name = upper(replace("${var.name_prefix}_yucca_to_resources", "-", "_"))
description = "${var.yucca_users_group} users → every resource=true group in this layer (auto-derived)."
enabled = true
rule {
name = upper(replace("${var.name_prefix}_yucca_to_resources", "-", "_"))
action = "accept"
protocol = "all"
bidirectional = false
enabled = true
sources = [data.netbird_group.yucca_users[0].id]
destinations = local.resource_group_ids
}
}
# ─── Networks (routed access into a site's underlying subnets) ───────────
# A Network groups one or more resources (subnets/hosts) reachable through a set
# of routing peers (router.peer_groups — e.g. a site's mgmt nodes). resources[*]
+9 -2
View File
@@ -19,13 +19,20 @@ variable "vault" {
}
variable "groups" {
description = "NetBird groups keyed by logical name. Groups start empty — membership comes from setup keys' auto_groups as peers register. Set `name` only to override the derived \"<NAME_PREFIX>_<KEY>\" (an override is normalized to UPPER_SNAKE too)."
description = "NetBird groups keyed by logical name. Groups start empty — membership comes from setup keys' auto_groups as peers register. Set `name` only to override the derived \"<NAME_PREFIX>_<KEY>\" (an override is normalized to UPPER_SNAKE too). Set `resource = true` to mark a group as a yucca *resource* group: every such group is auto-added as a destination of the generated yucca→resources policy (see var.yucca_users_group), so new resource groups are covered without editing a policy."
type = map(object({
name = optional(string)
name = optional(string)
resource = optional(bool, false)
}))
default = {}
}
variable "yucca_users_group" {
description = "Name of the pre-existing NetBird *users* group granted access to every group flagged `resource = true` in this layer. The module looks it up by name and generates a single `<NAME_PREFIX>_YUCCA_TO_RESOURCES` policy (bidirectional = false: yucca users only initiate to resources; resources can't reach back or each other), with destinations derived from all resource groups. Set null to not manage this policy. No-op when the layer owns no resource groups."
type = string
default = "yucca"
}
variable "external_groups" {
description = "Groups owned by another layer/stack, exposed here as logical key → NetBird group ID so policies/setup keys/networks can reference them without re-managing them. Intended for cross-layer references (e.g. a prod site layer consuming a group from prod/global via a terragrunt dependency). Empty by default; keys must not collide with var.groups."
type = map(string)
+5 -2
View File
@@ -5,8 +5,11 @@ terraform {
# provider block from NB_PAT (op://shared_tf/NETBIRD_TF_PAT); this module
# only declares the dependency.
netbird = {
source = "netbirdio/netbird"
version = "~> 0.0.9"
# FUTO-maintained fork (github.com/futo-org/terraform-provider-netbird).
# Only published to the Terraform registry, so the source is fully
# qualified — OpenTofu would otherwise look it up on registry.opentofu.org.
source = "registry.terraform.io/futo-org/netbird"
version = "~> 1.0"
}
# Writes minted setup keys into the per-env yucca_tf_<env> vault as the
# source-of-truth record. Auth via OP_SERVICE_ACCOUNT_TOKEN (op run).