community.openwrt.wg_interface module – Manage WireGuard/AmneziaWG identity keys, interface UCI sections and WAN ingress firewall rules

Note

This module is part of the community.openwrt collection (version 1.11.0).

It is not included in ansible-core. To check whether it is installed, run ansible-galaxy collection list.

To install it, use: ansible-galaxy collection install community.openwrt. You need further requirements to be able to use this module, see Requirements for details.

To use it in a playbook, specify: community.openwrt.wg_interface.

New in community.openwrt 1.10.0

Synopsis

  • Manages WireGuard/AmneziaWG identity keys, interface UCI sections and the WAN ingress firewall rule for each interface’s listen port.

  • The module performs the following steps in order:

    1. Determine the protocol of the ubus key server from the interfaces being kept (wireguard by default), failing if they request different protocols.

    1. Resolve the host identity key, reading it from every interface in managed_interfaces, failing on a hard mismatch (two distinct keys), otherwise reusing the single existing key or generating a fresh one via ubus, and deriving and reporting the public key.

    1. For each entry in interfaces, remove it when its interfaces[].state or state is absent, dropping the interface section, its peer sections and its WAN ingress rule (unless manage_firewall=false). Otherwise write its UCI section, with the identity key when it is in managed_interfaces (a fresh key on force_rekey=true) and the AWG options when its protocol is amneziawg, and ensure the WAN ingress ACCEPT rule for its listen port, or remove it when interfaces[].listen_port is not set (unless manage_firewall=false).

    1. Write the identity key to the other interfaces in managed_interfaces that miss one, or to all of them on force_rekey=true; names with no interface section yet are skipped.

    1. Resolve or generate a pre-shared key for each entry in peers, reusing an existing on-device PSK or generating one with the ubus genpsk method, and return them in psks, in the same order as peers. Generating a PSK is not reported as a change, since nothing is stored on the device.

  • For the available WireGuard UCI config options see https://openwrt.org/docs/guide-user/services/vpn/wireguard/start.

  • AmneziaWG support relies on the amneziawg ubus key server; see Requirements below.

Note

This module has a corresponding action plugin.

Requirements

The below requirements are needed on the host that executes this module.

Parameters

Parameter

Comments

firewall_rule_id_template

string

Ucode template for the UCI section id of the WAN ingress firewall rule.

Rendered with these variables in scope: iface_name (interfaces[].name), proto (interfaces[].proto or proto) and listen_port (interfaces[].listen_port).

Avoid using listen_port or proto here. A rule is found again by this id only, so with listen_port in it, the rule of a previous port is not removed when the port changes or is unset, nor when the interface is removed without interfaces[].listen_port. With proto in it, the rule of the previous protocol is not removed when the interface switches protocols.

Default: "{{ iface_name }}_in"

firewall_rule_name_template

string

Ucode template for the name/description of the WAN ingress firewall rule.

Rendered with these variables in scope: iface_name (interfaces[].name), proto (interfaces[].proto or proto) and listen_port (interfaces[].listen_port).

Default: "Allow-Wan-{{ proto }}: {{ iface_name }}"

force_psk_rekey

boolean

Regenerate every peer PSK even if one already exists on-device.

When true, new PSKs are returned in psks on every run. This is not reported as a change.

Choices:

  • false ← (default)

  • true

force_rekey

boolean

Regenerate the identity key even if one exists.

When true, the module is not idempotent. It writes a new key, and reports a change, on every run where a managed interface section exists.

Choices:

  • false ← (default)

  • true

interfaces

list / elements=dictionary / required

List of interface configurations to (re)write.

Each suboption below sets the UCI option of the same name. When a suboption is not set, that option is removed from an existing interface section, so options set by other means are lost. Other options of the section are left alone.

addresses

list / elements=string

List of addresses with prefix (v4/v6).

awg_tuning

dictionary

AmneziaWG tuning and 3.x/3.1 options (awg_* keys) applied verbatim.

Each awg_* key is the UCI name of an AmneziaWG parameter, for example awg_jc for Jc or awg_header_protection_key for HeaderProtectionKey, as read by the amneziawg protocol handler of https://github.com/this-username-has-been-taken/amneziawg-openwrt.

See https://docs.amnezia.org/documentation/amnezia-wg for what each parameter does and the values it accepts.

That handler knows these keys: integers awg_jc, awg_jmin, awg_jmax and awg_s1 to awg_s4; strings awg_h1 to awg_h4, awg_i1 to awg_i5, awg_header_protection_key, awg_content_padding_addition, awg_rekey_after_time, awg_rekey_timeout, awg_reject_after_time, awg_keepalive_timeout and awg_max_handshake_attempts; booleans awg_random_trailers and awg_disable_cookies.

Those keys that are not set are removed from the interface section. Other keys are written as given, so options of other AmneziaWG implementations can be set too.

Only meaningful when the interface protocol is amneziawg; ignored for wireguard.

When an interface is switched to wireguard, its awg_* options are left in place. WireGuard ignores them.

dns

list / elements=string

List of DNS servers advertised for the interface (OpenWrt dns option).

dns_metric

integer

DNS metric for the interface (OpenWrt dns_metric option).

listen_port

integer

Interface listen port.

metric

integer

Interface metric.

mtu

integer

Interface MTU.

name

string / required

Interface name.

peerdns

boolean

Whether to accept DNS servers provided by this interface (OpenWrt peerdns option).

Choices:

  • false

  • true

proto

string

Interface protocol.

Defaults to the module-wide proto when not set.

Choices:

  • "wireguard"

  • "amneziawg"

state

string

Whether the interface should be present or absent.

When absent, the interface section, its peer sections of either protocol and, unless manage_firewall=false, its WAN ingress rule are removed. The protocol of the interface on the device is used for the templates, when it has one.

Defaults to the module-wide state when not set.

Choices:

  • "present"

  • "absent"

manage_firewall

boolean

Whether to (re)write the WAN ingress ACCEPT rule for each interface listen port, and remove it along with the interface or when interfaces[].listen_port is not set.

Set to false when firewall management is handled elsewhere.

Choices:

  • false

  • true ← (default)

managed_interfaces

list / elements=string

Names of all the interfaces on the host that share the host identity key.

The module reads the private key from each of them, fails if they hold different keys (unless force_rekey=true), and writes the identity key to those missing one.

Names that have no interface section yet are skipped; they get the key when they are configured.

Use it when interfaces does not list every interface of the host, for instance when a play run with --tags configures only one point-to-point link out of several. Listing all of them here keeps that link on the same key as the others.

When not given, it is derived from interfaces, from the entries whose state is not absent. So when interfaces already lists every interface of the host, or when each interface may have its own key, this option can be omitted.

A run that only removes interfaces, with this option not given, does no key management.

peer_id_template

string

Ucode template for the peer UCI section id, used to locate a reusable on-device pre-shared key for idempotency.

Rendered with these variables in scope: iface_name (peers[].iface), section (peers[].section) and proto (peers[].proto, then the interface section’s proto, then proto).

Must render the same id as peer_id_template.

Default: "{% if (section): %}peer_{{ iface_name }}_{{ section }}{% else %}peer_{{ iface_name }}{% endif %}"

peers

list / elements=dictionary

Optional list of peer section skeletons. For each, the module resolves (but does not store) the pre-shared key: it reuses an existing on-device PSK or generates one with the ubus genpsk method.

Resolved keys are returned in psks, in the same order as this list, so the controller can distribute each shared key to both ends.

force_psk_rekey

boolean

Regenerate this peer PSK even if one already exists.

Choices:

  • false ← (default)

  • true

iface

string / required

Parent interface name.

proto

string

Peer protocol, used to render peer_id_template and to call the ubus key server.

Defaults to the proto of the interface section, as written by this task, then to the module-wide proto. community.openwrt.wg_peer resolves it the same way, so both modules render the same peer section id.

Choices:

  • "wireguard"

  • "amneziawg"

section

string

Peer section suffix, available as section in the peer_id_template template scope when resolving the pre-shared key.

proto

string

Module-wide protocol, used as the default for every entry in interfaces and peers whose own interfaces[].proto or peers[].proto is not set.

An explicitly-set interfaces[].proto or peers[].proto overrides this for that entry.

Choices:

  • "wireguard" ← (default)

  • "amneziawg"

state

string

Module-wide state, used as the default for every entry in interfaces whose own interfaces[].state is not set.

An explicitly-set interfaces[].state overrides this for that interface.

Choices:

  • "present" ← (default)

  • "absent"

Attributes

Attribute

Support

Description

check_mode

Support: full

Can run in check_mode and return changed status prediction without modifying target.

diff_mode

Support: full

Returns details on what has changed (or possibly needs changing in check_mode), when in diff mode.

platform

Platform: OpenWrt

Target platform for this module.

Notes

Note

  • The module writes UCI configuration only; it does not bring the interface up nor establish tunnels.

  • The identity key and peer PSKs are generated via ubus; the wireguard object is used as a fallback whenever the call to the amneziawg object fails or returns no result.

  • It is an intended design decision that all managed interfaces on a host share the same private key (and therefore the same public key), while each peer gets its own distinct pre-shared key. This keeps monitoring and peer mapping simple. The host is identified by one public key, and every peer link is identified by its unique PSK.

  • To give some interfaces a key of their own, configure them in a separate task, without listing them in managed_interfaces of the other tasks: each task then resolves the key of its own interfaces only. The module never takes a private key as a parameter, so that private keys never leave the device. To set a specific private key, use community.openwrt.uci on the private_key option of the interface.

  • psks holds the pre-shared keys in clear text. Set the no_log task keyword on tasks with peers to keep them out of the output and logs.

  • Template options (*_template) use ucode’s native template engine (Jinja-style {{ ... }} / {% ... %}).

  • This module does not work on OpenWrt 23.05, which has no rpcd-mod-wireguard package and therefore no wireguard ubus object.

Examples

# Hub: resolve/write the identity key and a hub interface section, then
# generate a per-spoke peer PSK for each of the two spokes
- community.openwrt.wg_interface:
    managed_interfaces: [wg0]
    interfaces:
      - name: wg0
        proto: wireguard
        listen_port: 51820
        addresses:
          - "10.8.0.1/24"
    peers:
      - iface: wg0
        section: spoke1
      - iface: wg0
        section: spoke2

# Two independent p2p links on one host: each link is its own interface with
# its own listen port, identity key (shared host-wide) and per-link peer PSK
- community.openwrt.wg_interface:
    managed_interfaces: [wg_l1, wg_l2]
    interfaces:
      - name: wg_l1
        proto: wireguard
        listen_port: 51821
        addresses:
          - "10.9.0.1/30"
      - name: wg_l2
        proto: wireguard
        listen_port: 51822
        addresses:
          - "10.10.0.1/30"
    peers:
      - iface: wg_l1
      - iface: wg_l2

# AmneziaWG hub. The tuning values below only show the syntax: generate your own,
# and use the same ones on every end of the link.
# See https://docs.amnezia.org/documentation/amnezia-wg
- community.openwrt.wg_interface:
    proto: amneziawg
    managed_interfaces: [awg0]
    interfaces:
      - name: awg0
        listen_port: 51830
        addresses:
          - "10.11.0.1/24"
        awg_tuning:
          awg_jc: 4
          awg_jmin: 40
          awg_jmax: 70
          awg_s1: 52
          awg_s2: 16
          awg_h1: 1166528911
          awg_h2: 2007365624
          awg_h3: 3125367892
          awg_h4: 4069137714
    peers:
      - iface: awg0
        section: spoke1

# WireGuard and AmneziaWG interfaces on one host, sharing the host identity key:
# one task per protocol, both listing every interface in managed_interfaces
- community.openwrt.wg_interface:
    proto: wireguard
    managed_interfaces: [wg0, awg0]
    interfaces:
      - name: wg0
        listen_port: 51820
        addresses:
          - "10.8.0.1/24"

- community.openwrt.wg_interface:
    proto: amneziawg
    managed_interfaces: [wg0, awg0]
    interfaces:
      - name: awg0
        listen_port: 51830
        addresses:
          - "10.11.0.1/24"
        awg_tuning:
          awg_jc: 4
          awg_jmin: 40
          awg_jmax: 70

Return Values

Common return values are documented here, the following are the fields unique to this module:

Key

Description

firewall_changed

boolean

True if any WAN ingress firewall rule was created, modified or removed.

Returned: always

interfaces

list / elements=string

Names of (re)written interface sections.

Returned: always

psks

list / elements=string

Resolved (not stored) peer pre-shared keys, as a list parallel to peers (psks[i] is the key for peers[i]). The controller zips the two lists to distribute each shared key to both ends.

Empty when peers is not given.

Returned: always

public_key

string

Host identity public key.

Empty when no key is managed, in a run that only removes interfaces without managed_interfaces.

Returned: always

removed_interfaces

list / elements=string

Names of interfaces removed because their interfaces[].state, or state, was absent.

Returned: always

Authors

  • Vladimir Ermakov (@vooon)