86 Commits
Author SHA1 Message Date
tklodaandGitHub c7a516cefa Merge pull request #36 from SunsetDrifter/fix/no-log-uri-token-leak
fix(roles): no_log on uri tasks to mask bearer token under -vvvv
2026-05-19 12:27:54 +02:00
Jack Carter 0ae9f8c657 fix(roles): add no_log to uri tasks to prevent bearer token leak under -vvvv
The three ansible.builtin.uri tasks in the export and configure roles
interpolate the PAT directly into the Authorization header. Under -vvvv
(or any inspection of the registered result), Ansible prints the
rendered task args dict including those headers, leaking the bearer
token in cleartext.

The remaining API calls in both roles go through
community.ansible_netbird.netbird_info, which already masks the token.
The three bare uri tasks were the only leak surface in the roles.

Adding no_log: true suppresses the rendered task args and the
registered response. loop_control.label is exempt from no_log, so the
per-iteration "name" still prints for progress visibility.

Detected by a token-leak audit that decrypts the vault and greps for
the actual token values across smoke + invariant runs under -vvvv on
both self-hosted and Cloud environments. Before this change, the audit
flagged 2 occurrences per invariant run; after, 0.

Fixes 3 leak sites:
  - roles/export/tasks/main.yml: "Fetch routers for each network"
  - roles/export/tasks/main.yml: "Fetch resources for each network"
  - roles/configure/tasks/main.yml: "Fetch routers for each network (preview diff)"
2026-05-19 12:16:04 +02:00
tklodaandGitHub 17b52ec57e Merge pull request #35 from SunsetDrifter/fix/relative-config-dir
fix: normalize relative config_dir in configure role
2026-04-30 11:21:47 +02:00
Jack Carter 29e2be840b fix: normalize relative config_dir in configure role
Ansible's `include_vars: dir:` resolves relative paths against the role's
own vars/ subdirectory rather than the playbook CWD, with no fallback to
CWD on miss. A relative config_dir (e.g. `./my_config`) caused the role
to fail at the "Load access control" task with:

    roles/configure/vars/./my_config/access_control directory does not exist

`include_vars: file:` happens to work because of its CWD fallback, so
"Load settings" succeeded and the failure first surfaced two tasks later
-- making the error look like a missing-file issue rather than a path
resolution one.

Canonicalize config_dir once at role entry via `expanduser | realpath`
into a role-internal `_config_dir_abs` fact, then route every runtime
reference through it. A separate fact name is required because
`include_role: vars:` precedence beats `set_fact` -- overwriting
config_dir in place would be silently re-shadowed by the role-vars input
on every reference.

Public input contract is unchanged: config_dir is still the documented
input and absolute paths behave identically.
2026-04-29 17:17:30 +02:00
Misha BraginandGitHub 5cae0beed8 fix: raise on unresolved group/peer/posture-check references 2026-04-21 15:17:08 +02:00
Misha BraginandGitHub fe2efcbec1 fix: heal DNS nameserver group domains=null state on apply 2026-04-21 15:16:28 +02:00
Jack Carter 6f18e2cf5f refactor: move DNS nameserver null-coercion to module layer
Code-review follow-up on bbea38e. Two small cleanups, no behavior change:

- plugins/module_utils/netbird_api.py: revert update_nameserver_group's
  PUT-body construction for `domains` back to the partial-update pattern
  (`if domains is not None`) used by every other update_* method. The
  helper is now a pure HTTP primitive again, so future modules can rely
  on consistent partial-update semantics for this surface.

- plugins/modules/netbird_dns.py: coerce `domains` and `groups` from
  None to [] at the module layer, right before building update_params
  and calling the API helper. This keeps the null-heal intent local to
  the DNS module (where the dashboard-crash reason lives) and applies
  symmetrically to both list fields, closing the asymmetry between
  nsgroup_needs_update (already symmetric) and the PUT body.

Net effect is identical to bbea38e: installer-seeded `domains: null`
still heals to `[]` on apply, the dashboard still loads, the sister
repo's smoke/extended-CRUD assertions still pass. Only the layer at
which the coercion lives has moved.

Coexists cleanly with open PR #33 (fix/strict-name-resolution): both
branches touch roles/configure/tasks/main.yml at non-overlapping hunks.
2026-04-17 18:43:24 +02:00
Jack Carter bbea38eae0 fix: heal DNS nameserver group domains=null state on apply
A self-hosted NetBird dashboard crashed when loading the DNS → Nameservers
page because the installer-default "Public DNS" nameserver group was stored
with `domains: null` and the dashboard frontend does not null-guard that
field. The collection's apply cycle was preserving the null state rather
than healing it.

Four layers of fix:

- roles/export/templates/export/dns/nameservers.yml.j2 — always emit
  `domains: []` on export instead of omitting the key when the API
  returned null. Ensures the exported YAML carries an explicit list
  into the apply cycle.

- roles/configure/tasks/main.yml — coerce `item.domains is null` to
  `[]` via `default([], true)` before passing to the module, so that
  a hand-edited YAML with `domains: null` still produces `[]`.

- plugins/module_utils/netbird_api.py — `update_nameserver_group` now
  always includes `domains` in the PUT body and coerces `None → []`.
  Previously it skipped the field on `None`, which preserved the
  backend's null state.

- plugins/modules/netbird_dns.py — `nsgroup_needs_update` now treats
  backend `domains: null` (or `groups: null`) as a heal-eligible change
  against a list-valued desired state, so a PUT fires to coerce the
  field. Previously it used `current.get('domains') or []` which
  silently equated null with [] and returned "no change".

With these together, a `make safe-apply-test` cycle heals the
installer-seeded null state, and the dashboard loads. Verified against
netbird.cybersunset.net.
2026-04-17 18:03:39 +02:00
Jack CarterandClaude Opus 4.7 63e3fd9dd6 fix: pass context to netbird_resolve_names for DNS zone distribution_groups
Without context, an unresolved group in a DNS zone's distribution_groups
would raise a generic "Unknown group 'foo'" error. Naming the zone makes
the error actionable.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 17:04:50 +02:00
Jack CarterandClaude Opus 4.7 27595eee61 fix: raise on unresolved group/peer/posture-check references instead of silent drop
The configure role's name resolver previously used dict.get(name, name)
fallbacks, so any unresolvable reference in YAML config was silently
passed through to the API as if it were a valid ID. On the server side,
the reference was discarded — producing half-applied policies whose
rules ended up with sources: null (or destinations/auto_groups wiped).

Common failure mode: a typo in a group name ships a broken firewall
rule that reports changed=true but has no source constraint.

This change makes the resolver strict:

- Unknown name that is ALSO not an existing ID -> AnsibleFilterError
  with a message naming the resource, field, and unresolved value.
- Known name -> resolves to ID (unchanged).
- Value that matches an existing ID -> passes through (preserves
  backward compatibility for YAML configs that use raw IDs).

Applies to groups in policy sources/destinations, setup_key auto_groups,
policy source_posture_checks, peer refs in policy source_resource/
destination_resource, and network router.peer / resource.groups.

Also: the /api/networks/{id}/routers endpoint returns router.peer as
the peer's HOSTNAME rather than its canonical name. The peer_ids map
(configure role) and peer_id_map (export role) now include hostname
aliases so round-trips stay idempotent.

Files:
- plugins/filter/netbird_resolve.py: strict _resolve_names,
  _resolve_resource_ref, new _resolve_peer_id helper
- roles/configure/tasks/main.yml: peer_ids merges hostname->id
  aliases with name->id
- roles/export/tasks/main.yml: peer_id_map merges hostname->name
  aliases with id->name

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 16:59:52 +02:00
Misha BraginandGitHub 39066a36c2 fix: export peer-based policy sources via sourceResource fallback 2026-04-17 14:03:42 +02:00
Misha BraginandGitHub e48f815067 fix: null-guard DNS nameserver domains/groups on export and update 2026-04-17 14:03:26 +02:00
Jack Carter 9108d2e540 fix: null-guard DNS nameserver domains/groups on export and update
Two related crashes when a nameserver group has no domains or groups:
the API stores unset fields as null (not []), but the export template
and netbird_dns module both assumed list.

- roles/export/templates/export/dns/nameservers.yml.j2: add
  `is not none` guards on ns.groups and ns.domains. Matches the pattern
  already used on ns.description (line 14) and across zones.yml.j2.
  Without this, `None | length` crashes the export template and blocks
  every test that does a full export (iac, roundtrip, strict).

- plugins/modules/netbird_dns.py: in nsgroup_needs_update, use
  `current.get('domains') or []` instead of `current.get('domains', [])`.
  The .get default only applies when the key is missing, not when the
  value is None. Without this, `set(None)` crashes when diffing against
  a live API group that has `domains: null`.

Verified on live self-hosted NetBird: iac-selfhosted, roundtrip-selfhosted,
and strict-selfhosted now all pass (previously failed at the DNS export
or DNS update step).
2026-04-17 10:47:28 +02:00
tklodaandGitHub 61a80834a7 Merge pull request #1 from SunsetDrifter/fix/policy-peer-source-roundtrip
fix: resolve peer-type source/destination resources on roundtrip
2026-04-17 10:38:06 +02:00
Jack Carter 02e2c94ddb fix: resolve peer-type source/destination resources on roundtrip
PR#31 fixed export silently dropping peer-based policy sources, but
collapsed them into sources: [peer_name]. The configure role resolver
only maps sources against group_ids, so peer names fell through
unresolved. The netbird_policy module then sent literal peer names to
the API, which silently accepted them and wiped sourceResource --
turning the exported YAML into a policy with no source at all (reported
as changed: true).

Changes:
- Export template emits source_resource: {name, type: peer} for
  peer-type resources (keeps host/domain/subnet as {id, type}).
- _resolve_policy in netbird_resolve accepts peer_ids and converts
  {name, type: peer} to {id, type: peer}; non-peer refs pass through.
- configure/tasks/main.yml passes peer_ids to the policy resolve call
  (the map is already built on line 214 for networks).

Roundtrip verified on live NetBird: sourceResource preserved; apply
reports ok (idempotent) instead of false changed: true.
2026-04-17 10:28:50 +02:00
Tomas KlodaandClaude Opus 4.6 843d349012 fix: export peer-based policy sources via sourceResource fallback
The NetBird API uses camelCase `sourceResource` (not snake_case
`source_resource`) in JSON responses. When a specific peer is used as a
policy source, `sources` is empty/null and `sourceResource` contains
`{"id": "<peer-id>", "type": "peer"}`.

The old template only checked `rule.source_resource` (snake_case), which
never matched the API's camelCase field, causing peer-based sources to be
silently dropped from the exported YAML.

The fix:
- Checks both `source_resource` and `sourceResource` field names
- For peer-type resources: converts to a `sources` entry with the peer
  name resolved via `peer_id_map` (compatible with the configure role)
- For other resource types (domain, subnet, host): kept as
  `source_resource` / `destination_resource` blocks
- Ensures `sources` and `source_resource` are never output together
  (matching the API's mutual exclusivity constraint)
- Applies the same logic to destination_resource / destinationResource

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-16 11:47:27 +02:00
tklodaandGitHub 1af557165b Merge pull request #30 from tkloda/differ
Fixing differ
2026-03-31 23:16:37 +02:00
Tomas Kloda ec2a4f1ec9 normalization 2026-03-31 23:08:46 +02:00
Tomas Kloda 98e1b98ce0 another debug 2026-03-31 23:04:59 +02:00
Tomas Kloda afd8b36897 adding debug 2026-03-31 23:00:12 +02:00
tklodaandGitHub 98e52d683b Merge pull request #29 from tkloda/differ
fixing false diff
2026-03-31 22:50:24 +02:00
Tomas Kloda da74682398 fixing false diff 2026-03-31 22:49:24 +02:00
tklodaandGitHub 8203dfa9a4 Merge pull request #28 from tkloda/differ
use filter fqdn instead
2026-03-31 22:39:05 +02:00
Tomas Kloda 8ae2a014ea use filter fqdn instead 2026-03-31 22:38:15 +02:00
tklodaandGitHub 41043f0839 Merge pull request #27 from tkloda/differ
bump version to 1.2.0 for filter plugin support
2026-03-31 22:34:02 +02:00