--- # ============================================================================= # 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: "" 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 }}- # {{ bat.display_name }} Ground Station: netbird up --management-url {{ netbird_protocol }}://{{ netbird_domain }} \ --setup-key <{{ bat.name }}-gs-key> \ --hostname gs-{{ bat.name }}- {% endfor %} # Dev Team: netbird up --management-url {{ netbird_protocol }}://{{ netbird_domain }} \ --setup-key \ --hostname dev- ============================================