282 lines
11 KiB
YAML
282 lines
11 KiB
YAML
---
|
|
# =============================================================================
|
|
# NetBird v1.6 - User Provisioning
|
|
# =============================================================================
|
|
# Creates users with embedded IdP and stores generated passwords.
|
|
# Requires: service user PAT in vault.yml (see setup-bootstrap.yml)
|
|
#
|
|
# Run:
|
|
# ansible-playbook -i inventory.yml setup-users.yml --ask-vault-pass
|
|
#
|
|
# Optional variables:
|
|
# -e "dry_run=true" Preview changes without creating users
|
|
# =============================================================================
|
|
|
|
- name: Provision NetBird Users
|
|
hosts: netbird_servers
|
|
become: false
|
|
gather_facts: true
|
|
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"
|
|
dry_run: false
|
|
|
|
pre_tasks:
|
|
# =========================================================================
|
|
# Validate Prerequisites
|
|
# =========================================================================
|
|
- name: Validate service PAT is provided
|
|
ansible.builtin.assert:
|
|
that:
|
|
- vault_netbird_service_pat is defined
|
|
- vault_netbird_service_pat | length > 0
|
|
fail_msg: |
|
|
Service PAT not configured!
|
|
Run setup-bootstrap.yml first, then add PAT to vault.yml
|
|
|
|
- name: Verify API connectivity with PAT
|
|
ansible.builtin.uri:
|
|
url: "{{ netbird_api_url }}/users"
|
|
method: GET
|
|
headers:
|
|
Authorization: "Token {{ vault_netbird_service_pat }}"
|
|
Accept: "application/json"
|
|
validate_certs: false
|
|
status_code: [200]
|
|
register: api_check
|
|
delegate_to: localhost
|
|
|
|
- name: Display connection status
|
|
ansible.builtin.debug:
|
|
msg: "API connection successful. Found {{ api_check.json | length }} existing users."
|
|
|
|
tasks:
|
|
# =========================================================================
|
|
# Fetch Existing State
|
|
# =========================================================================
|
|
- name: Get existing users
|
|
ansible.builtin.uri:
|
|
url: "{{ netbird_api_url }}/users"
|
|
method: GET
|
|
headers:
|
|
Authorization: "Token {{ vault_netbird_service_pat }}"
|
|
Accept: "application/json"
|
|
validate_certs: false
|
|
status_code: [200]
|
|
register: existing_users_response
|
|
delegate_to: localhost
|
|
|
|
- name: Extract existing user emails
|
|
ansible.builtin.set_fact:
|
|
existing_user_emails: "{{ existing_users_response.json | map(attribute='email') | list }}"
|
|
|
|
- name: Get existing groups
|
|
ansible.builtin.uri:
|
|
url: "{{ netbird_api_url }}/groups"
|
|
method: GET
|
|
headers:
|
|
Authorization: "Token {{ vault_netbird_service_pat }}"
|
|
Accept: "application/json"
|
|
validate_certs: false
|
|
status_code: [200]
|
|
register: existing_groups_response
|
|
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: "{{ existing_groups_response.json }}"
|
|
|
|
# =========================================================================
|
|
# Resolve Auto-Groups for Users
|
|
# =========================================================================
|
|
- name: Resolve auto-groups for users
|
|
ansible.builtin.set_fact:
|
|
resolved_users: "{{ resolved_users | default([]) + [user_with_groups] }}"
|
|
vars:
|
|
battalion_group: >-
|
|
{%- if item.battalion is defined and item.battalion -%}
|
|
{%- if item.type | default('pilot') == 'pilot' -%}
|
|
{{ item.battalion }}-pilots
|
|
{%- else -%}
|
|
{{ item.battalion }}-ground-stations
|
|
{%- endif -%}
|
|
{%- endif -%}
|
|
final_auto_groups: >-
|
|
{{ item.auto_groups | default([]) + ([battalion_group | trim] if battalion_group | trim else []) }}
|
|
resolved_group_ids: >-
|
|
{{ final_auto_groups | map('extract', group_id_map) | select('defined') | list }}
|
|
user_with_groups:
|
|
email: "{{ item.email }}"
|
|
name: "{{ item.name }}"
|
|
role: "{{ item.role | default('user') }}"
|
|
auto_groups: "{{ resolved_group_ids }}"
|
|
auto_group_names: "{{ final_auto_groups }}"
|
|
battalion: "{{ item.battalion | default(none) }}"
|
|
skip: "{{ item.email in existing_user_emails }}"
|
|
loop: "{{ netbird_users | default([]) }}"
|
|
|
|
# =========================================================================
|
|
# Display Plan
|
|
# =========================================================================
|
|
- name: Count users to process
|
|
ansible.builtin.set_fact:
|
|
users_to_create: "{{ resolved_users | default([]) | rejectattr('skip') | list }}"
|
|
users_to_skip: "{{ resolved_users | default([]) | selectattr('skip') | list }}"
|
|
|
|
- name: Display provisioning plan
|
|
ansible.builtin.debug:
|
|
msg: |
|
|
============================================
|
|
User Provisioning Plan
|
|
============================================
|
|
Mode: {{ 'DRY RUN' if dry_run else 'EXECUTE' }}
|
|
|
|
Users to CREATE ({{ users_to_create | length }}):
|
|
{% for user in users_to_create %}
|
|
- {{ user.email }}
|
|
Name: {{ user.name }}
|
|
Role: {{ user.role }}
|
|
Groups: {{ user.auto_group_names | join(', ') or 'None' }}
|
|
{% endfor %}
|
|
{% if users_to_create | length == 0 %}
|
|
(none)
|
|
{% endif %}
|
|
|
|
Users to SKIP - already exist ({{ users_to_skip | length }}):
|
|
{% for user in users_to_skip %}
|
|
- {{ user.email }}
|
|
{% endfor %}
|
|
{% if users_to_skip | length == 0 %}
|
|
(none)
|
|
{% endif %}
|
|
============================================
|
|
|
|
- name: End play in dry run mode
|
|
ansible.builtin.meta: end_play
|
|
when: dry_run | bool
|
|
|
|
- name: End play if no users to create
|
|
ansible.builtin.meta: end_play
|
|
when: users_to_create | length == 0
|
|
|
|
# =========================================================================
|
|
# Create Users
|
|
# =========================================================================
|
|
- name: Create credentials directory
|
|
ansible.builtin.file:
|
|
path: "{{ playbook_dir }}/files/credentials"
|
|
state: directory
|
|
mode: "0700"
|
|
delegate_to: localhost
|
|
|
|
- name: Create new users
|
|
ansible.builtin.uri:
|
|
url: "{{ netbird_api_url }}/users"
|
|
method: POST
|
|
headers:
|
|
Authorization: "Token {{ vault_netbird_service_pat }}"
|
|
Content-Type: "application/json"
|
|
Accept: "application/json"
|
|
body_format: json
|
|
body:
|
|
email: "{{ item.email }}"
|
|
name: "{{ item.name }}"
|
|
role: "{{ item.role }}"
|
|
auto_groups: "{{ item.auto_groups }}"
|
|
is_service_user: false
|
|
validate_certs: false
|
|
status_code: [200, 201]
|
|
loop: "{{ users_to_create }}"
|
|
register: created_users
|
|
delegate_to: localhost
|
|
|
|
# =========================================================================
|
|
# Store Credentials
|
|
# =========================================================================
|
|
- name: Build credentials list
|
|
ansible.builtin.set_fact:
|
|
user_credentials: "{{ user_credentials | default([]) + [credential] }}"
|
|
vars:
|
|
matching_user: "{{ resolved_users | selectattr('email', 'equalto', item.json.email) | first }}"
|
|
credential:
|
|
email: "{{ item.json.email }}"
|
|
name: "{{ item.json.name }}"
|
|
password: "{{ item.json.password | default('N/A') }}"
|
|
user_id: "{{ item.json.id }}"
|
|
role: "{{ item.json.role }}"
|
|
created_at: "{{ ansible_date_time.iso8601 }}"
|
|
groups: "{{ matching_user.auto_group_names | default([]) }}"
|
|
loop: "{{ created_users.results }}"
|
|
when: item.json is defined
|
|
|
|
- name: Save credentials to file
|
|
ansible.builtin.copy:
|
|
content: |
|
|
---
|
|
# =============================================================================
|
|
# NetBird User Credentials
|
|
# =============================================================================
|
|
# Generated: {{ ansible_date_time.iso8601 }}
|
|
# Instance: {{ netbird_domain }}
|
|
#
|
|
# WARNING: Store securely! Passwords cannot be retrieved again.
|
|
# =============================================================================
|
|
|
|
users:
|
|
{% for user in user_credentials %}
|
|
- email: "{{ user.email }}"
|
|
name: "{{ user.name }}"
|
|
password: "{{ user.password }}"
|
|
user_id: "{{ user.user_id }}"
|
|
role: "{{ user.role }}"
|
|
groups:
|
|
{% for group in user.groups %}
|
|
- "{{ group }}"
|
|
{% endfor %}
|
|
{% if user.groups | length == 0 %}
|
|
[]
|
|
{% endif %}
|
|
{% endfor %}
|
|
dest: "{{ playbook_dir }}/files/credentials/users-{{ ansible_date_time.date }}.yml"
|
|
mode: "0600"
|
|
delegate_to: localhost
|
|
when:
|
|
- user_credentials is defined
|
|
- user_credentials | length > 0
|
|
|
|
# =========================================================================
|
|
# Display Summary
|
|
# =========================================================================
|
|
- name: Display provisioning summary
|
|
ansible.builtin.debug:
|
|
msg: |
|
|
============================================
|
|
User Provisioning Complete!
|
|
============================================
|
|
|
|
Created Users ({{ user_credentials | default([]) | length }}):
|
|
{% for user in user_credentials | default([]) %}
|
|
{{ user.email }}
|
|
Password: {{ user.password }}
|
|
Role: {{ user.role }}
|
|
Groups: {{ user.groups | join(', ') or 'None' }}
|
|
{% endfor %}
|
|
|
|
Credentials saved to:
|
|
{{ playbook_dir }}/files/credentials/users-{{ ansible_date_time.date }}.yml
|
|
|
|
IMPORTANT:
|
|
1. Share passwords securely with users
|
|
2. Encrypt or delete credentials file after distribution:
|
|
ansible-vault encrypt files/credentials/users-{{ ansible_date_time.date }}.yml
|
|
3. Users should change passwords on first login
|
|
|
|
Login URL: https://{{ netbird_api_host }}
|
|
============================================
|
|
when: user_credentials is defined
|