Comprehensive Ansible automation for managing Netmaker networks and external client devices via the Netmaker API. This module provides unified, idempotent infrastructure-as-code for your Netmaker WireGuard mesh networks.
- Unified module for networks and external client devices
- Fully idempotent operations - safe to run multiple times
- Complete CRUD lifecycle (Create, Read, Update, Delete)
- Auto-discovery of ingress gateways
- WireGuard config files for devices without netclient software
- Check mode (dry-run) capability
- Modern tooling with Python UV and Just task runner
- Both master key and username/password authentication
- SSL/TLS certificate validation control
# Install the collection from Ansible Galaxy
ansible-galaxy collection install oriolrius.netmaker# Clone the repository
git clone https://github.com/oriolrius/netmaker-ansible-automation.git
cd netmaker-ansible-automation
# Install UV package manager (if not already installed)
curl -LsSf https://astral.sh/uv/install.sh | sh
# Install project dependencies
just installCreate a .env file from the example template:
cp .env.example .envEdit .env and add your Netmaker credentials:
# .env
NETMAKER_ENDPOINT=https://api.netmaker.example.com
NETMAKER_MASTER_KEY=your_actual_master_key_hereImportant: The .env file contains sensitive credentials and should never be committed to version control. Make sure it's listed in .gitignore.
---
- name: Manage Netmaker Infrastructure
hosts: localhost
tasks:
- name: Create IoT network
oriolrius.netmaker.netmaker_management:
resource_type: network
name: iot-network
state: present
base_url: https://api.netmaker.example.com
master_key: "{{ netmaker_master_key }}"
addressrange: "10.102.0.0/24"
defaultmtu: 1420
- name: Create IoT device
oriolrius.netmaker.netmaker_management:
resource_type: extclient
name: sensor-01
network: iot-network
ingress_gateway_id: auto
state: present
base_url: https://api.netmaker.example.com
master_key: "{{ netmaker_master_key }}"
enabled: true# List all available commands
just
# Network operations
just create # Create networks from playbook
just delete my-network # Delete specific network
# Device operations
just manage-devices # Create/update external client devices
just delete-device sensor-01 # Delete specific deviceThe master key is the recommended authentication method for automation. To find it:
# SSH to your Netmaker server
ssh your-netmaker-server
# Look for MASTER_KEY in your configuration
grep MASTER_KEY docker-compose.yml
# or
grep MASTER_KEY netmaker.envNetmaker has three types of resources:
- Networks - Virtual WireGuard networks (managed via API)
- External Clients (Devices) - WireGuard devices that get config files without netclient software (managed via API)
- Hosts - Servers running the netclient software (auto-registered, not created via API)
This module manages Networks and External Client Devices - perfect for IoT devices, phones, laptops, and any device that needs WireGuard access without installing netclient.
This custom Ansible module manages networks and external client devices through a unified interface.
All operations require these base parameters:
| Parameter | Required | Type | Default | Description |
|---|---|---|---|---|
resource_type |
Yes | str | - | Resource type: network or extclient |
name |
Yes | str | - | Resource name/identifier |
state |
No | str | present |
present to create/update, absent to delete |
base_url |
Yes | str | - | Netmaker API URL (e.g., https://api.netmaker.example.com) |
master_key |
No* | str | - | Master key for authentication |
username |
No* | str | oriol |
Username for user/password authentication |
password |
No* | str | - | Password for user/password authentication |
validate_certs |
No | bool | true |
Validate SSL certificates |
*Either master_key or password is required for authentication.
Use these parameters when resource_type: network:
| Parameter | Type | Default | Description |
|---|---|---|---|
addressrange |
str | - | IPv4 CIDR range (e.g., "10.100.0.0/24") |
addressrange6 |
str | - | IPv6 CIDR range |
defaultmtu |
int | - | Default MTU size for the network |
defaultkeepalive |
int | - | Default keepalive interval in seconds |
defaultextclientdns |
str | - | Default external client DNS server |
defaultinterface |
str | - | Default network interface |
defaultpostdown |
str | - | Command to run after interface goes down |
defaultpostup |
str | - | Command to run after interface comes up |
islocal |
bool | false |
Whether this is a local-only network |
Use these parameters when resource_type: extclient:
| Parameter | Type | Default | Description |
|---|---|---|---|
network |
str | - | Required. Network ID the device should join |
ingress_gateway_id |
str | auto |
Gateway node ID (use auto for automatic discovery) |
dns |
str | - | DNS server for the device |
extraallowedips |
list | - | Additional allowed IPs for routing |
enabled |
bool | true |
Whether the device is enabled |
postup |
str | - | Command to run after WireGuard interface comes up |
postdown |
str | - | Command to run after WireGuard interface goes down |
The module returns structured data you can use in subsequent tasks:
| Key | Type | Description |
|---|---|---|
changed |
bool | Whether the resource was modified |
resource |
dict | Full resource data (when state is present) |
msg |
str | Human-readable status message |
- name: Create IoT network
netmaker_management:
resource_type: network
name: iot-network
state: present
base_url: "{{ netmaker_url }}"
master_key: "{{ netmaker_master_key }}"
addressrange: "10.102.0.0/24"
defaultmtu: 1420
defaultkeepalive: 25- name: Update network MTU
netmaker_management:
resource_type: network
name: iot-network
state: present
base_url: "{{ netmaker_url }}"
master_key: "{{ netmaker_master_key }}"
defaultmtu: 1280 # Change MTU- name: Delete network
netmaker_management:
resource_type: network
name: old-network
state: absent
base_url: "{{ netmaker_url }}"
master_key: "{{ netmaker_master_key }}"External clients are WireGuard devices that connect to your network without needing the netclient software. Perfect for IoT devices, phones, laptops, etc.
- name: Create IoT sensor device
netmaker_management:
resource_type: extclient
name: sensor-01
network: iot-network
ingress_gateway_id: auto # Automatically find ingress gateway
state: present
base_url: "{{ netmaker_url }}"
master_key: "{{ netmaker_master_key }}"
enabled: true- name: Create IoT devices
netmaker_management:
resource_type: extclient
name: "{{ item }}"
network: iot-network
ingress_gateway_id: auto
state: present
base_url: "{{ netmaker_url }}"
master_key: "{{ netmaker_master_key }}"
enabled: true
loop:
- sensor-01
- sensor-02
- camera-01
- gateway-device- name: Create device with specific ingress gateway
netmaker_management:
resource_type: extclient
name: special-device
network: iot-network
ingress_gateway_id: "bef77574-f11e-46cf-b1c1-98888a2189c4"
state: present
base_url: "{{ netmaker_url }}"
master_key: "{{ netmaker_master_key }}"
dns: "8.8.8.8"
enabled: true- name: Disable device temporarily
netmaker_management:
resource_type: extclient
name: sensor-01
network: iot-network
state: present
base_url: "{{ netmaker_url }}"
master_key: "{{ netmaker_master_key }}"
enabled: false- name: Delete obsolete device
netmaker_management:
resource_type: extclient
name: old-sensor
network: iot-network
state: absent
base_url: "{{ netmaker_url }}"
master_key: "{{ netmaker_master_key }}"- name: Deploy IoT infrastructure
hosts: localhost
vars:
netmaker_url: "https://api.netmaker.example.com"
netmaker_master_key: "{{ lookup('env', 'NETMAKER_MASTER_KEY') }}"
tasks:
# Create network
- name: Create IoT network
netmaker_management:
resource_type: network
name: iot-network
state: present
base_url: "{{ netmaker_url }}"
master_key: "{{ netmaker_master_key }}"
addressrange: "10.102.0.0/24"
defaultmtu: 1420
defaultkeepalive: 25
# Deploy devices
- name: Create IoT devices
netmaker_management:
resource_type: extclient
name: "{{ item.name }}"
network: iot-network
ingress_gateway_id: auto
state: present
base_url: "{{ netmaker_url }}"
master_key: "{{ netmaker_master_key }}"
enabled: true
loop:
- { name: 'sensor-01' }
- { name: 'sensor-02' }
- { name: 'camera-01' }
- { name: 'controller-01' }The justfile provides convenient shortcuts for common operations:
just install # Install Python dependencies with UV
just check # Verify Ansible installationjust create # Create networks (uses playbook vars)
just delete my-network # Delete specific network by name
just create-check # Dry run for create networks
just create-verbose # Create networks with verbose outputjust manage-devices # Create/update devices (uses playbook vars)
just devices-check # Dry run for manage devices
just devices-verbose # Manage devices with verbose outputjust run playbook.yml [args] # Run any playbook with custom arguments
just lint # Syntax check all playbooks
just clean # Clean Python cache filesThe module includes sample playbooks in the playbooks/ directory:
Creates or updates Netmaker networks.
Usage:
just create
# or
uv run ansible-playbook playbooks/create_network.ymlWhat it does:
- Creates networks with defined IPv4 address ranges
- Sets MTU and keepalive defaults
- Idempotent - safe to run multiple times
Deletes a specific network by name.
Usage:
just delete my-network
# or
uv run ansible-playbook playbooks/delete_network.yml -e network_name=my-networkWarning: Deleting a network will disconnect all devices from that network.
Creates or updates external client devices.
Usage:
just manage-devices
# or
uv run ansible-playbook playbooks/manage_devices.ymlWhat it does:
- Creates WireGuard device configurations
- Auto-discovers ingress gateways
- Assigns IP addresses automatically
- Updates existing device configurations
- Fully idempotent
Configuration: Edit the tasks section to define your devices.
This project uses master key authentication exclusively. The master key is loaded automatically from the .env file.
Setup:
-
Create
.envfile from the example:cp .env.example .env
-
Add your master key to
.env:NETMAKER_ENDPOINT=https://api.netmaker.example.com NETMAKER_MASTER_KEY=your_actual_master_key_here
-
Run commands - the master key is automatically loaded:
just create just manage-devices
How It Works:
The justfile automatically loads the NETMAKER_MASTER_KEY from .env and passes it to all Ansible playbooks. You don't need to specify it manually.
Manual Override:
If needed, you can override the master key on the command line:
uv run ansible-playbook playbooks/create_network.yml \
-e netmaker_master_key=different_keyThe module is fully idempotent - you can safely run the same playbook multiple times:
# First run - creates devices
just manage-devices
# Output: changed=true for new devices
# Second run - no changes needed
just manage-devices
# Output: changed=false, devices already exist
# Edit playbook to disable a device, then run
just manage-devices
# Output: changed=true (updates that device)The module automatically:
- Detects existing resources
- Compares desired vs actual configuration
- Only makes changes when needed
- Reports accurate changed status
Symptom: ERROR! couldn't resolve module/action 'netmaker_management'
Solution: Ensure you're running commands from the correct directory:
cd /home/oriol/iotgw-ng/ansible/netmaker
ls ansible.cfg # Must existSymptom: ModuleNotFoundError: No module named 'requests'
Solution:
just installSymptom: SSL: CERTIFICATE_VERIFY_FAILED
Solution: For development/testing only:
validate_certs: falseWarning: Never disable certificate validation in production.
Symptom: Authentication failed or 401 Unauthorized
Solution: Verify your master key:
# On Netmaker server
ssh your-netmaker-server
grep MASTER_KEY docker-compose.ymlSymptom: No ingress gateway found in network
Solution: Ensure your network has at least one node configured as an ingress gateway. You can check this via the Netmaker UI or by listing nodes and looking for isingressgateway: true.
Enable verbose output to see detailed API interactions:
just create-verbose
# or
uv run ansible-playbook playbooks/manage_devices.yml -vvvansible/netmaker/
├── README.md # This file
├── justfile # Task runner with command shortcuts
├── ansible.cfg # Ansible configuration
├── pyproject.toml # Python dependencies (UV)
├── .env.example # Example environment configuration
├── .env # Your actual credentials (DO NOT COMMIT)
├── library/
│ └── netmaker_management.py # Main Ansible module
└── playbooks/
├── create_network.yml # Create/update networks
├── delete_network.yml # Delete network by name
├── manage_devices.yml # Create/update external client devices
└── delete_device.yml # Delete device by name
Security Note: Always ensure .env is listed in .gitignore to prevent committing sensitive credentials.
- Never commit
.envto version control - Use
.env.exampleas a template for others - Store production credentials in secure vaults (HashiCorp Vault, AWS Secrets Manager, etc.)
- Rotate master keys periodically
Preview changes before applying them:
just devices-checkLet the module find the ingress gateway automatically:
ingress_gateway_id: auto # RecommendedUse consistent naming for easy management:
- sensor-01, sensor-02, sensor-03 # Environment sensors
- camera-01, camera-02 # Security cameras
- controller-main, controller-backup # Control systemsAlways keep .env out of version control:
# Add to .gitignore
echo ".env" >> .gitignoreAfter creating external client devices with this module, you can download their WireGuard configuration files from:
- Netmaker UI: Navigate to External Clients section
- API:
GET /api/extclients/{network}/{clientid}/conf - CLI on Netmaker server
These configs can be imported into any WireGuard client (phone app, desktop, IoT device, etc.).
- Ansible Galaxy Collection
- GitHub Repository
- Netmaker Documentation
- Netmaker API Reference
- Ansible Module Development
- UV Package Manager
- Just Command Runner
- WireGuard
This project uses GitHub Actions to automatically publish to Ansible Galaxy when versions are updated.
-
Bump the version in both
galaxy.ymlandpyproject.toml:# Using the helper script just bump-version 1.1.0 # Or manually edit both files
-
Commit and push the version change:
git add galaxy.yml pyproject.toml git commit -m "Bump version to 1.1.0" git push origin main -
GitHub Actions will automatically:
- Verify versions match between
galaxy.ymlandpyproject.toml - Build the collection
- Publish to Ansible Galaxy with
--forceflag - Create a GitHub Release with the collection tarball
- Verify versions match between
The workflow ensures version alignment:
- Compares
galaxy.ymlversion withpyproject.tomlversion - Fails if versions don't match
- Only publishes when versions are synchronized
You can also manually trigger the workflow from GitHub Actions tab.
GNU General Public License v3.0+
Collection Version: 1.0.0 Documentation Updated: 2025-10-16