feat(ansible): add OpenWrt dlink AP configuration
Add community.openwrt collection, dlink host to inventory, openwrt role with system/network/firewall tasks, and two playbooks: dlink-init.yml for one-time bootstrap from factory IP, and openwrt.yml for ongoing idempotent configuration. Network: MGMT untagged + LAN (vlan2) tagged on WAN port trunk to MikroTik ether3. Firewall zones replace factory WAN/LAN with mgmt (input ACCEPT) and lan (forward ACCEPT, AP mode).
This commit is contained in:
+87
-15
@@ -1,20 +1,92 @@
|
||||
## RouterOS Ansible
|
||||
# Ansible
|
||||
|
||||
This directory contains the new Ansible automation for the MikroTik router.
|
||||
Idempotent configuration management for the home-lab network devices.
|
||||
|
||||
- Transport: RouterOS API (`community.routeros` collection), not SSH CLI scraping.
|
||||
- Layout: one playbook (`playbooks/routeros.yml`) importing domain task files from `tasks/`.
|
||||
- Goal: idempotent convergence using `community.routeros.api_modify` for managed paths.
|
||||
## Devices
|
||||
|
||||
### Quick start
|
||||
| Host | Group | IP | Playbook |
|
||||
|---|---|---|---|
|
||||
| crs418 (MikroTik CRS418) | `mikrotik` | 192.168.255.10 | `playbooks/routeros.yml` |
|
||||
| dlink (OpenWrt AP) | `openwrt` | 192.168.255.11 | `playbooks/openwrt.yml` |
|
||||
|
||||
1. Install dependencies:
|
||||
- `ansible-galaxy collection install -r ansible/requirements.yml`
|
||||
- `python -m pip install librouteros hvac`
|
||||
2. Configure secret references in `ansible/vars/routeros-secrets.yml`.
|
||||
3. Store required fields in OpenBao under configured KV path.
|
||||
4. Export token (`OPENBAO_TOKEN` or `VAULT_TOKEN`).
|
||||
5. Run:
|
||||
- `ANSIBLE_CONFIG=ansible/ansible.cfg ansible-playbook ansible/playbooks/routeros.yml`
|
||||
Both devices are reachable on the MGMT network (192.168.255.0/24) once fully set up.
|
||||
|
||||
More details and design rationale: `docs/ansible/routeros-design.md`.
|
||||
## Dependencies
|
||||
|
||||
```bash
|
||||
ansible-galaxy collection install -r requirements.yml
|
||||
pip install librouteros hvac
|
||||
```
|
||||
|
||||
Collections used:
|
||||
|
||||
- `community.routeros >= 3.16.0` — MikroTik API modules
|
||||
- `community.hashi_vault >= 7.1.0` — OpenBao/Vault secret lookup
|
||||
- `community.openwrt >= 1.0.0` — OpenWrt UCI and shell modules
|
||||
|
||||
## MikroTik (routeros)
|
||||
|
||||
Secrets are fetched at runtime from OpenBao. No credentials are stored in files.
|
||||
|
||||
```bash
|
||||
export VAULT_TOKEN=... # or OPENBAO_TOKEN
|
||||
ansible-playbook playbooks/routeros.yml
|
||||
```
|
||||
|
||||
Secret layout expected in OpenBao (KVv2, mount `secret`):
|
||||
|
||||
| Path | Fields |
|
||||
|---|---|
|
||||
| `routeros_api` | `username`, `password` |
|
||||
| `wan_pppoe` | `username`, `password` |
|
||||
| `router_tailscale` | `container_password` |
|
||||
|
||||
## OpenWrt dlink AP
|
||||
|
||||
The dlink needs a one-time initialisation before it can be managed through MikroTik.
|
||||
There are two playbooks:
|
||||
|
||||
### Step 1 — `dlink-init.yml` (once, PC directly connected)
|
||||
|
||||
Run this while your PC is plugged into one of the dlink **LAN ports** with the
|
||||
device still on its factory IP (192.168.1.1). MikroTik must **not** be in the
|
||||
picture yet.
|
||||
|
||||
What it does:
|
||||
- Reconfigures switch0 so the **WAN port** becomes a VLAN trunk:
|
||||
- untagged → VLAN 1 (MGMT, 192.168.255.0/24)
|
||||
- tagged → VLAN 2 (LAN, 192.168.0.0/24)
|
||||
- Adds `mgmt` interface: static 192.168.255.11/24, gateway 192.168.255.10
|
||||
- Reconfigures `lan` to a bridge on eth0.2 with no IP (AP mode)
|
||||
- Removes routed `wan`/`wan6` interfaces
|
||||
- Commits and reloads network in the background
|
||||
|
||||
After the reload the device is no longer reachable at 192.168.1.1.
|
||||
|
||||
```bash
|
||||
ansible-playbook playbooks/dlink-init.yml
|
||||
```
|
||||
|
||||
### Step 2 — connect dlink WAN port to MikroTik ether3
|
||||
|
||||
Plug the **dlink WAN port** into **MikroTik ether3**.
|
||||
|
||||
If the MikroTik config hasn't been applied yet, do it now:
|
||||
|
||||
```bash
|
||||
export VAULT_TOKEN=...
|
||||
ansible-playbook playbooks/routeros.yml
|
||||
```
|
||||
|
||||
MikroTik ether3 is configured to send MGMT traffic untagged and VLAN 2 (LAN)
|
||||
tagged, which matches what dlink expects on its WAN port.
|
||||
|
||||
### Step 3 — `openwrt.yml` (ongoing, via MikroTik)
|
||||
|
||||
All subsequent runs connect to 192.168.255.11 through MikroTik:
|
||||
|
||||
```bash
|
||||
ansible-playbook playbooks/openwrt.yml
|
||||
```
|
||||
|
||||
This is the idempotent main playbook. Run it any time to converge configuration.
|
||||
|
||||
@@ -4,3 +4,9 @@ all:
|
||||
hosts:
|
||||
crs418:
|
||||
ansible_host: 192.168.255.10
|
||||
openwrt:
|
||||
hosts:
|
||||
dlink:
|
||||
ansible_host: 192.168.255.11
|
||||
ansible_user: root
|
||||
ansible_ssh_port: 22
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
---
|
||||
# One-time initialisation playbook for the dlink OpenWrt AP.
|
||||
#
|
||||
# Run this while your PC is directly connected to a dlink LAN port
|
||||
# (factory IP 192.168.1.1, no MikroTik in the picture yet).
|
||||
#
|
||||
# What it does:
|
||||
# - Replaces the entire network config (switch VLANs, devices, interfaces)
|
||||
# - Replaces the entire firewall config (mgmt/lan zones, no WAN)
|
||||
# - Reloads network and firewall in the background
|
||||
#
|
||||
# After this playbook finishes the device is no longer reachable at 192.168.1.1.
|
||||
# Plug the WAN port into MikroTik ether3 and use playbooks/openwrt.yml for all
|
||||
# further configuration.
|
||||
|
||||
- name: dlink — one-time network initialisation
|
||||
hosts: openwrt
|
||||
gather_facts: false
|
||||
vars:
|
||||
ansible_host: "192.168.1.1"
|
||||
ansible_user: root
|
||||
|
||||
tasks:
|
||||
- name: Verify connectivity
|
||||
community.openwrt.ping:
|
||||
|
||||
- name: Configure network (switch VLANs, devices, interfaces)
|
||||
community.openwrt.uci:
|
||||
command: import
|
||||
merge: false
|
||||
config: network
|
||||
value: |
|
||||
package network
|
||||
|
||||
config interface 'loopback'
|
||||
option device 'lo'
|
||||
option proto 'static'
|
||||
list ipaddr '127.0.0.1/8'
|
||||
|
||||
config globals 'globals'
|
||||
option ula_prefix 'fd4d:508e:899a::/48'
|
||||
|
||||
config switch
|
||||
option name 'switch0'
|
||||
option reset '1'
|
||||
option enable_vlan '1'
|
||||
|
||||
config switch_vlan
|
||||
option device 'switch0'
|
||||
option vlan '1'
|
||||
option vid '1'
|
||||
option description 'mgmt'
|
||||
option ports '4 6t'
|
||||
|
||||
config switch_vlan
|
||||
option device 'switch0'
|
||||
option vlan '2'
|
||||
option vid '2'
|
||||
option description 'lan'
|
||||
option ports '0 1 2 3 4t 6t'
|
||||
|
||||
config device
|
||||
option name 'br-lan'
|
||||
option type 'bridge'
|
||||
list ports 'eth0.2'
|
||||
|
||||
config interface 'mgmt'
|
||||
option device 'eth0.1'
|
||||
option proto 'static'
|
||||
option ipaddr '192.168.255.11/24'
|
||||
option gateway '192.168.255.10'
|
||||
option dns '192.168.0.1'
|
||||
|
||||
config interface 'lan'
|
||||
option device 'br-lan'
|
||||
option proto 'none'
|
||||
|
||||
- name: Commit network config
|
||||
community.openwrt.uci:
|
||||
command: commit
|
||||
key: network
|
||||
|
||||
- name: Configure firewall (mgmt/lan zones, no WAN)
|
||||
community.openwrt.uci:
|
||||
command: import
|
||||
merge: false
|
||||
config: firewall
|
||||
value: |
|
||||
package firewall
|
||||
|
||||
config defaults
|
||||
option syn_flood '1'
|
||||
option input 'REJECT'
|
||||
option output 'ACCEPT'
|
||||
option forward 'REJECT'
|
||||
|
||||
config zone
|
||||
option name 'mgmt'
|
||||
list network 'mgmt'
|
||||
option input 'ACCEPT'
|
||||
option output 'ACCEPT'
|
||||
option forward 'REJECT'
|
||||
|
||||
config zone
|
||||
option name 'lan'
|
||||
list network 'lan'
|
||||
option input 'REJECT'
|
||||
option output 'ACCEPT'
|
||||
option forward 'ACCEPT'
|
||||
|
||||
config rule
|
||||
option name 'Allow-ICMP-mgmt'
|
||||
option src 'mgmt'
|
||||
option proto 'icmp'
|
||||
option target 'ACCEPT'
|
||||
|
||||
- name: Commit firewall config
|
||||
community.openwrt.uci:
|
||||
command: commit
|
||||
key: firewall
|
||||
|
||||
- name: Reload network in background (device will drop off 192.168.1.1)
|
||||
community.openwrt.nohup:
|
||||
command: /etc/init.d/network reload
|
||||
ignore_unreachable: true
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
# Main OpenWrt playbook. Connects to dlink on its permanent management IP
|
||||
# (192.168.255.11 via MikroTik ether3). Run dlink-init.yml first if the
|
||||
# device has not been initialised yet.
|
||||
- name: Configure OpenWrt
|
||||
hosts: openwrt
|
||||
gather_facts: false
|
||||
|
||||
roles:
|
||||
- role: openwrt
|
||||
@@ -3,3 +3,5 @@ collections:
|
||||
version: ">=3.16.0"
|
||||
- name: community.hashi_vault
|
||||
version: ">=7.1.0"
|
||||
- name: community.openwrt
|
||||
version: ">=1.0.0"
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
---
|
||||
# Hostname for the AP
|
||||
openwrt_hostname: dlink
|
||||
|
||||
# Timezone (POSIX TZ string used by OpenWrt)
|
||||
openwrt_timezone: CET-1CEST,M3.5.0,M10.5.0/3
|
||||
|
||||
# Management interface and IP (statically assigned on VLAN 1 / eth0.1)
|
||||
openwrt_mgmt_ip: 192.168.255.11
|
||||
openwrt_mgmt_prefix: 24
|
||||
openwrt_mgmt_gateway: 192.168.255.10
|
||||
|
||||
# DNS servers for the AP itself
|
||||
openwrt_dns_servers:
|
||||
- 192.168.0.1
|
||||
|
||||
# SSH authorised keys (list of public key strings)
|
||||
openwrt_ssh_authorized_keys: []
|
||||
|
||||
# NTP servers
|
||||
openwrt_ntp_servers:
|
||||
- 0.pl.pool.ntp.org
|
||||
- 1.pl.pool.ntp.org
|
||||
|
||||
# Packages to install
|
||||
openwrt_packages: []
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
- name: Reload network
|
||||
community.openwrt.nohup:
|
||||
command: /etc/init.d/network reload
|
||||
ignore_unreachable: true
|
||||
|
||||
- name: Reload firewall
|
||||
community.openwrt.service:
|
||||
name: firewall
|
||||
state: restarted
|
||||
|
||||
- name: Reload wireless
|
||||
community.openwrt.command:
|
||||
cmd: wifi reload
|
||||
@@ -0,0 +1,51 @@
|
||||
---
|
||||
# This device is a pure AP — no routing, no NAT, no internet-facing interface.
|
||||
#
|
||||
# Zones:
|
||||
# mgmt — management interface (192.168.255.11)
|
||||
# input: ACCEPT (SSH, ping reachable from MGMT network)
|
||||
# forward: REJECT (nothing routes through mgmt)
|
||||
#
|
||||
# lan — client bridge (eth0.2, wireless clients)
|
||||
# input: REJECT (clients cannot SSH into the AP itself)
|
||||
# forward: ACCEPT (client traffic passes through to MikroTik,
|
||||
# which does all actual firewalling)
|
||||
#
|
||||
# No forwarding rules between zones — traffic in/out of each zone goes
|
||||
# directly to/from MikroTik over the trunk, not through this device.
|
||||
|
||||
- name: Configure firewall
|
||||
community.openwrt.uci:
|
||||
command: import
|
||||
merge: false
|
||||
config: firewall
|
||||
value: |
|
||||
package firewall
|
||||
|
||||
config defaults
|
||||
option syn_flood '1'
|
||||
option input 'REJECT'
|
||||
option output 'ACCEPT'
|
||||
option forward 'REJECT'
|
||||
|
||||
config zone
|
||||
option name 'mgmt'
|
||||
list network 'mgmt'
|
||||
option input 'ACCEPT'
|
||||
option output 'ACCEPT'
|
||||
option forward 'REJECT'
|
||||
|
||||
config zone
|
||||
option name 'lan'
|
||||
list network 'lan'
|
||||
option input 'REJECT'
|
||||
option output 'ACCEPT'
|
||||
option forward 'ACCEPT'
|
||||
|
||||
config rule
|
||||
option name 'Allow-ICMP-mgmt'
|
||||
option src 'mgmt'
|
||||
option proto 'icmp'
|
||||
option target 'ACCEPT'
|
||||
|
||||
notify: Reload firewall
|
||||
@@ -0,0 +1,19 @@
|
||||
---
|
||||
- name: Preflight — verify connectivity
|
||||
ansible.builtin.import_tasks: preflight.yml
|
||||
|
||||
- name: System configuration
|
||||
ansible.builtin.import_tasks: system.yml
|
||||
|
||||
- name: Network configuration
|
||||
ansible.builtin.import_tasks: network.yml
|
||||
|
||||
- name: Firewall configuration
|
||||
ansible.builtin.import_tasks: firewall.yml
|
||||
|
||||
- name: Wireless configuration
|
||||
ansible.builtin.import_tasks: wireless.yml
|
||||
|
||||
- name: Package management
|
||||
ansible.builtin.import_tasks: packages.yml
|
||||
when: openwrt_packages | length > 0
|
||||
@@ -0,0 +1,88 @@
|
||||
---
|
||||
# Network layout:
|
||||
# MikroTik ether3 ↔ dlink WAN port (switch0 port4)
|
||||
# MikroTik sends MGMT traffic untagged, vlan2 (LAN) and vlan5 (IOT) tagged.
|
||||
#
|
||||
# switch0 VLAN table:
|
||||
# VLAN 1 (MGMT): CPU(6) tagged, WAN(4) untagged → eth0.1 → mgmt
|
||||
# VLAN 2 (LAN): CPU(6) tagged, WAN(4) tagged, LAN1-4(0-3) untagged → eth0.2 → br-lan → lan
|
||||
# VLAN 5 (IOT): CPU(6) tagged, WAN(4) tagged → eth0.5 → br-iot → iot
|
||||
#
|
||||
# Interfaces:
|
||||
# mgmt — static 192.168.255.11/24 on eth0.1, management
|
||||
# lan — bridge (br-lan) on eth0.2, LAN clients via LAN ports
|
||||
# iot — bridge (br-iot) on eth0.5, IoT clients via wifi only
|
||||
|
||||
- name: Configure network
|
||||
community.openwrt.uci:
|
||||
command: import
|
||||
merge: false
|
||||
config: network
|
||||
value: |
|
||||
package network
|
||||
|
||||
config interface 'loopback'
|
||||
option device 'lo'
|
||||
option proto 'static'
|
||||
list ipaddr '127.0.0.1/8'
|
||||
|
||||
config globals 'globals'
|
||||
option ula_prefix 'fd4d:508e:899a::/48'
|
||||
|
||||
config switch
|
||||
option name 'switch0'
|
||||
option reset '1'
|
||||
option enable_vlan '1'
|
||||
|
||||
config switch_vlan
|
||||
option device 'switch0'
|
||||
option vlan '1'
|
||||
option vid '1'
|
||||
option description 'mgmt'
|
||||
option ports '4 6t'
|
||||
|
||||
config switch_vlan
|
||||
option device 'switch0'
|
||||
option vlan '2'
|
||||
option vid '2'
|
||||
option description 'lan'
|
||||
option ports '0 1 2 3 4t 6t'
|
||||
|
||||
config switch_vlan
|
||||
option device 'switch0'
|
||||
option vlan '5'
|
||||
option vid '5'
|
||||
option description 'iot'
|
||||
option ports '4t 6t'
|
||||
|
||||
config device
|
||||
option name 'br-lan'
|
||||
option type 'bridge'
|
||||
list ports 'eth0.2'
|
||||
|
||||
config interface 'mgmt'
|
||||
option device 'eth0.1'
|
||||
option proto 'static'
|
||||
option ipaddr '{{ openwrt_mgmt_ip }}/{{ openwrt_mgmt_prefix }}'
|
||||
option gateway '{{ openwrt_mgmt_gateway }}'
|
||||
option dns '{{ openwrt_dns_servers | join(" ") }}'
|
||||
|
||||
config interface 'lan'
|
||||
option device 'br-lan'
|
||||
option proto 'none'
|
||||
|
||||
config device
|
||||
option name 'br-iot'
|
||||
option type 'bridge'
|
||||
list ports 'eth0.5'
|
||||
|
||||
config interface 'iot'
|
||||
option device 'br-iot'
|
||||
option proto 'none'
|
||||
|
||||
notify: Reload network
|
||||
|
||||
- name: Commit network config
|
||||
community.openwrt.uci:
|
||||
command: commit
|
||||
key: network
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
- name: Install packages
|
||||
community.openwrt.opkg:
|
||||
name: "{{ item }}"
|
||||
state: present
|
||||
update_cache: true
|
||||
loop: "{{ openwrt_packages }}"
|
||||
@@ -0,0 +1,11 @@
|
||||
---
|
||||
- name: Verify connectivity to OpenWrt device
|
||||
community.openwrt.ping:
|
||||
|
||||
- name: Gather OpenWrt facts
|
||||
community.openwrt.setup:
|
||||
register: openwrt_facts
|
||||
|
||||
- name: Show device info
|
||||
ansible.builtin.debug:
|
||||
msg: "Managing {{ inventory_hostname }} ({{ openwrt_facts.ansible_facts.ansible_system | default('OpenWrt') }})"
|
||||
@@ -0,0 +1,30 @@
|
||||
---
|
||||
- name: Set hostname
|
||||
community.openwrt.uci:
|
||||
command: set
|
||||
key: system.@system[0].hostname
|
||||
value: "{{ openwrt_hostname }}"
|
||||
|
||||
- name: Set timezone
|
||||
community.openwrt.uci:
|
||||
command: set
|
||||
key: system.@system[0].timezone
|
||||
value: "{{ openwrt_timezone }}"
|
||||
|
||||
- name: Configure NTP servers
|
||||
community.openwrt.uci:
|
||||
command: set
|
||||
key: system.ntp.server
|
||||
value: "{{ openwrt_ntp_servers }}"
|
||||
|
||||
- name: Commit system config
|
||||
community.openwrt.uci:
|
||||
command: commit
|
||||
key: system
|
||||
|
||||
- name: Set SSH authorized keys
|
||||
community.openwrt.uci:
|
||||
command: set
|
||||
key: "dropbear.@dropbear[0].authorized_keys"
|
||||
value: "{{ openwrt_ssh_authorized_keys | join('\n') }}"
|
||||
when: openwrt_ssh_authorized_keys | length > 0
|
||||
Reference in New Issue
Block a user