Files
netbird-iac/ansible/netbird/setup-groups.yml
2026-02-15 18:37:15 +02:00

368 lines
13 KiB
YAML

---
# =============================================================================
# NetBird v1.6 - Battalion Group & Access Control Setup
# =============================================================================
# Creates:
# - Groups for each battalion (pilots + ground stations)
# - Dev team group with full access
# - Setup keys with auto-group assignment
# - Access control policies (battalion isolation + dev access)
#
# Prerequisites:
# 1. NetBird deployed and running (playbook-ssl.yml or playbook-no-ssl.yml)
# 2. Admin user created via dashboard
# 3. PAT (Personal Access Token) generated from dashboard
#
# Run:
# ansible-playbook -i inventory.yml setup-groups.yml --ask-vault-pass
# =============================================================================
- name: Configure NetBird Battalion Access Control
hosts: netbird_servers
become: false
gather_facts: false
vars_files:
- group_vars/netbird_servers.yml
- group_vars/vault.yml
vars:
# For SSL-IP mode, use server IP; for domain mode, use netbird_domain
netbird_api_host: "{{ hostvars[inventory_hostname].ansible_host | default(netbird_domain) }}"
netbird_api_url: "https://{{ netbird_api_host }}/api"
# Use PAT from vault, or allow override via command line
netbird_pat: "{{ vault_netbird_service_pat }}"
pre_tasks:
- name: Validate PAT is provided
ansible.builtin.assert:
that:
- netbird_pat is defined
- netbird_pat | length > 0
fail_msg: |
Service PAT not configured in vault.yml!
1. Create service user + PAT in dashboard
2. Store in vault: ansible-vault edit group_vars/vault.yml
Set: vault_netbird_service_pat: "<your-token>"
tasks:
# =========================================================================
# Get Existing Groups (to avoid duplicates)
# =========================================================================
- name: Get existing groups
ansible.builtin.uri:
url: "{{ netbird_api_url }}/groups"
method: GET
headers:
Authorization: "Token {{ netbird_pat }}"
Accept: "application/json"
validate_certs: false
status_code: [200]
register: existing_groups
delegate_to: localhost
- name: Extract existing group names
ansible.builtin.set_fact:
existing_group_names: "{{ existing_groups.json | map(attribute='name') | list }}"
# =========================================================================
# Create Battalion Groups
# =========================================================================
- name: Create battalion pilot groups
ansible.builtin.uri:
url: "{{ netbird_api_url }}/groups"
method: POST
headers:
Authorization: "Token {{ netbird_pat }}"
Content-Type: "application/json"
body_format: json
body:
name: "{{ item.name }}-pilots"
validate_certs: false
status_code: [200, 201]
loop: "{{ battalions }}"
when: "item.name + '-pilots' not in existing_group_names"
register: pilot_groups_created
delegate_to: localhost
- name: Create battalion ground station groups
ansible.builtin.uri:
url: "{{ netbird_api_url }}/groups"
method: POST
headers:
Authorization: "Token {{ netbird_pat }}"
Content-Type: "application/json"
body_format: json
body:
name: "{{ item.name }}-ground-stations"
validate_certs: false
status_code: [200, 201]
loop: "{{ battalions }}"
when: "item.name + '-ground-stations' not in existing_group_names"
register: gs_groups_created
delegate_to: localhost
- name: Create dev team group
ansible.builtin.uri:
url: "{{ netbird_api_url }}/groups"
method: POST
headers:
Authorization: "Token {{ netbird_pat }}"
Content-Type: "application/json"
body_format: json
body:
name: "{{ dev_team_group }}"
validate_certs: false
status_code: [200, 201]
when: "dev_team_group not in existing_group_names"
delegate_to: localhost
# =========================================================================
# Re-fetch Groups to Get IDs
# =========================================================================
- name: Get all groups with IDs
ansible.builtin.uri:
url: "{{ netbird_api_url }}/groups"
method: GET
headers:
Authorization: "Token {{ netbird_pat }}"
Accept: "application/json"
validate_certs: false
status_code: [200]
register: all_groups
delegate_to: localhost
- name: Build group ID mapping
ansible.builtin.set_fact:
group_id_map: "{{ group_id_map | default({}) | combine({item.name: item.id}) }}"
loop: "{{ all_groups.json }}"
- name: Get All group ID
ansible.builtin.set_fact:
all_group_id: "{{ (all_groups.json | selectattr('name', 'equalto', 'All') | first).id }}"
# =========================================================================
# Create Setup Keys
# =========================================================================
- name: Get existing setup keys
ansible.builtin.uri:
url: "{{ netbird_api_url }}/setup-keys"
method: GET
headers:
Authorization: "Token {{ netbird_pat }}"
Accept: "application/json"
validate_certs: false
status_code: [200]
register: existing_keys
delegate_to: localhost
- name: Extract existing setup key names
ansible.builtin.set_fact:
existing_key_names: "{{ existing_keys.json | map(attribute='name') | list }}"
- name: Create setup keys for battalion pilots
ansible.builtin.uri:
url: "{{ netbird_api_url }}/setup-keys"
method: POST
headers:
Authorization: "Token {{ netbird_pat }}"
Content-Type: "application/json"
body_format: json
body:
name: "{{ item.name }}-pilot-key"
type: "reusable"
expires_in: 31536000 # 1 year in seconds
revoked: false
auto_groups:
- "{{ group_id_map[item.name + '-pilots'] }}"
usage_limit: 0 # unlimited
validate_certs: false
status_code: [200, 201]
loop: "{{ battalions }}"
when: "item.name + '-pilot-key' not in existing_key_names"
register: pilot_keys
delegate_to: localhost
- name: Create setup keys for battalion ground stations
ansible.builtin.uri:
url: "{{ netbird_api_url }}/setup-keys"
method: POST
headers:
Authorization: "Token {{ netbird_pat }}"
Content-Type: "application/json"
body_format: json
body:
name: "{{ item.name }}-gs-key"
type: "reusable"
expires_in: 31536000
revoked: false
auto_groups:
- "{{ group_id_map[item.name + '-ground-stations'] }}"
usage_limit: 0
validate_certs: false
status_code: [200, 201]
loop: "{{ battalions }}"
when: "item.name + '-gs-key' not in existing_key_names"
register: gs_keys
delegate_to: localhost
- name: Create setup key for dev team
ansible.builtin.uri:
url: "{{ netbird_api_url }}/setup-keys"
method: POST
headers:
Authorization: "Token {{ netbird_pat }}"
Content-Type: "application/json"
body_format: json
body:
name: "dev-team-key"
type: "reusable"
expires_in: 31536000
revoked: false
auto_groups:
- "{{ group_id_map[dev_team_group] }}"
usage_limit: 0
validate_certs: false
status_code: [200, 201]
when: "'dev-team-key' not in existing_key_names"
register: dev_key
delegate_to: localhost
# =========================================================================
# Create Access Control Policies
# =========================================================================
- name: Get existing policies
ansible.builtin.uri:
url: "{{ netbird_api_url }}/policies"
method: GET
headers:
Authorization: "Token {{ netbird_pat }}"
Accept: "application/json"
validate_certs: false
status_code: [200]
register: existing_policies
delegate_to: localhost
- name: Extract existing policy names
ansible.builtin.set_fact:
existing_policy_names: "{{ existing_policies.json | map(attribute='name') | list }}"
- name: Create battalion internal access policies
ansible.builtin.uri:
url: "{{ netbird_api_url }}/policies"
method: POST
headers:
Authorization: "Token {{ netbird_pat }}"
Content-Type: "application/json"
body_format: json
body:
name: "{{ item.display_name }} - Internal Access"
description: "Allow {{ item.display_name }} pilots to access their ground stations"
enabled: true
rules:
- name: "{{ item.name }}-pilot-to-gs"
description: "Pilots can access ground stations"
enabled: true
sources:
- "{{ group_id_map[item.name + '-pilots'] }}"
destinations:
- "{{ group_id_map[item.name + '-ground-stations'] }}"
bidirectional: true
protocol: "all"
action: "accept"
validate_certs: false
status_code: [200, 201]
loop: "{{ battalions }}"
when: "item.display_name + ' - Internal Access' not in existing_policy_names"
delegate_to: localhost
- name: Create dev team full access policy
ansible.builtin.uri:
url: "{{ netbird_api_url }}/policies"
method: POST
headers:
Authorization: "Token {{ netbird_pat }}"
Content-Type: "application/json"
body_format: json
body:
name: "Dev Team - Full Access"
description: "Dev team can access all peers for troubleshooting"
enabled: true
rules:
- name: "dev-full-access"
description: "Dev team has access to all peers"
enabled: true
sources:
- "{{ group_id_map[dev_team_group] }}"
destinations:
- "{{ all_group_id }}"
bidirectional: true
protocol: "all"
action: "accept"
validate_certs: false
status_code: [200, 201]
when: "'Dev Team - Full Access' not in existing_policy_names"
delegate_to: localhost
# =========================================================================
# Fetch and Display Setup Keys
# =========================================================================
- name: Get all setup keys
ansible.builtin.uri:
url: "{{ netbird_api_url }}/setup-keys"
method: GET
headers:
Authorization: "Token {{ netbird_pat }}"
Accept: "application/json"
validate_certs: false
status_code: [200]
register: final_keys
delegate_to: localhost
- name: Display configuration summary
ansible.builtin.debug:
msg: |
============================================
Battalion Access Control Configured!
============================================
Groups Created:
{% for bat in battalions %}
- {{ bat.name }}-pilots
- {{ bat.name }}-ground-stations
{% endfor %}
- {{ dev_team_group }}
Access Control Matrix:
{% for bat in battalions %}
[{{ bat.display_name }}]
{{ bat.name }}-pilots <--> {{ bat.name }}-ground-stations
{% endfor %}
[Dev Team]
{{ dev_team_group }} --> All (full access)
Setup Keys (use these to register peers):
{% for key in final_keys.json %}
{% if key.name.endswith('-pilot-key') or key.name.endswith('-gs-key') or key.name == 'dev-team-key' %}
{{ key.name }}: {{ key.key }}
{% endif %}
{% endfor %}
Peer Registration Commands:
{% for bat in battalions %}
# {{ bat.display_name }} Pilot:
netbird up --management-url {{ netbird_protocol }}://{{ netbird_domain }} \
--setup-key <{{ bat.name }}-pilot-key> \
--hostname pilot-{{ bat.name }}-<callsign>
# {{ bat.display_name }} Ground Station:
netbird up --management-url {{ netbird_protocol }}://{{ netbird_domain }} \
--setup-key <{{ bat.name }}-gs-key> \
--hostname gs-{{ bat.name }}-<location>
{% endfor %}
# Dev Team:
netbird up --management-url {{ netbird_protocol }}://{{ netbird_domain }} \
--setup-key <dev-team-key> \
--hostname dev-<name>
============================================