OPNsense Unbound Host Overrides

This role manages DNS host overrides (local DNS records) in OPNsense Unbound resolver via the REST API.

ARA Ansible DNS Grafana HTTPS JSON OPNsense REST API

OPNsense Unbound Host Overrides Role

Overview

This role manages DNS host overrides (local DNS records) in OPNsense Unbound resolver via the REST API. It provides full lifecycle management: creating new overrides, updating existing overrides when configuration changes, and deleting orphaned overrides that exist on OPNsense but are no longer defined in the vars files.

Purpose

  • Local DNS Management: Define custom DNS names that resolve to specific IP addresses
  • Code as Configuration: Define DNS overrides in YAML
  • API-Based: Reliable automation via OPNsense REST API
  • Full Lifecycle Management: Create, update, and delete overrides
  • Idempotent: Safe to run multiple times using hostname+domain as unique key
  • Automatic Cleanup: Removes orphaned overrides not defined in vars
  • Domain Blocking: Block malicious TLDs by pointing them to null addresses

Requirements

  • Ansible 2.9 or higher
  • OPNsense firewall with API access enabled
  • API key and secret stored in Ansible Vault
  • Network connectivity to OPNsense (VLAN10)
  • OPNsense user with Unbound permissions

What are Host Overrides?

Host overrides are local DNS records that:

  • Override public DNS for specific hostnames
  • Provide internal name resolution for local services
  • Block domains by pointing them to invalid addresses
  • Support A, AAAA, and MX record types

Example: server.homelab → 192.168.x.x

Role Variables

Required Variables

VariableRequiredDescription
vault_opnsense_bjoffrey_user_api_keyYesOPNsense API key (in vault)
vault_opnsense_bjoffrey_user_api_secretYesOPNsense API secret (in vault)
opnsense_unbound_host_overrides_listYesList of host overrides

Optional Variables

VariableDefaultDescription
opnsense_unbound_host_overrides_validate_certstrueValidate SSL certificates

Override Structure

Each override in the list has these fields:

Required fields:

- hostname: "grafana"        # Hostname portion (use "*" for wildcard)
  domain: "localdomain"      # Domain portion
  server: "192.168.x.x"     # IP address to resolve to

Optional fields:

  enabled: "1"               # Enable (1) or disable (0)
  rr: "A"                    # Record type: A, AAAA, or MX
  ttl: ""                    # Time to live (empty = default)
  mxprio: ""                 # MX priority (only for MX records)
  mx: ""                     # Mail server (only for MX records)
  description: "Grafana"     # Human-readable description

Complete example:

opnsense_unbound_host_overrides_list:
  # Block malicious TLD
  - hostname: "*"
    domain: "zip"
    server: "192.168.x.x"
    description: "Block zip domain"

  # Internal service
  - hostname: "grafana"
    domain: "localdomain"
    server: "192.168.x.x"
    description: "Grafana monitoring"

  # Management interface
  - hostname: "grafana-mgmt"
    domain: "localdomain"
    server: "192.168.x.x"
    description: "Grafana mgmt interface"

Idempotency

The role uses hostname+domain as the unique identifier:

  • server.homelab maps to a specific UUID on OPNsense
  • Create: New hostname+domain combination → creates override
  • Update: Existing hostname+domain with changed fields → updates override
  • Delete: hostname+domain on OPNsense but not in vars → deletes override

Important: Removing an override from your vars file will delete it from OPNsense on the next run.

Dependencies

This role has no dependencies on other Ansible roles, but requires:

  • OPNsense firewall with API enabled
  • Unbound DNS service enabled
  • API key with Unbound management permissions
  • Ansible Vault for storing API credentials

Example Playbook

Basic Usage

---
- name: Configure OPNsense Unbound Host Overrides
  hosts: mint-vm
  gather_facts: false

  vars_files:
    - ../../roles/opnsense_unbound_host_overrides/vars/host_overrides.yml

  tasks:
    - name: Configure host overrides
      ansible.builtin.include_role:
        name: opnsense_unbound_host_overrides

Inline Variables

---
- name: Configure Unbound Overrides
  hosts: mint-vm
  gather_facts: false

  vars:
    opnsense_unbound_host_overrides_list:
      - hostname: "myservice"
        domain: "localdomain"
        server: "192.168.x.x"
        description: "My custom service"

  roles:
    - opnsense_unbound_host_overrides

What This Role Does

  1. Fetches existing overrides via /api/unbound/settings/searchHostOverride
  2. Builds hostname+domain → UUID mapping for idempotency
  3. Creates new overrides via /api/unbound/settings/addHostOverride
  4. Updates existing overrides via /api/unbound/settings/setHostOverride/{uuid}
  5. Deletes orphaned overrides via /api/unbound/settings/delHostOverride/{uuid}
  6. Reconfigures Unbound via /api/unbound/service/reconfigure to apply changes
  7. Displays summary of configured overrides

OPNsense API Endpoints

Search Host Overrides

GET /api/unbound/settings/searchHostOverride
Authorization: Basic (API key:secret)

Returns all host overrides with their UUIDs.

Add Host Override

POST /api/unbound/settings/addHostOverride
Authorization: Basic (API key:secret)
Content-Type: application/json

Request body:

{
  "host": {
    "enabled": "1",
    "hostname": "grafana",
    "domain": "localdomain",
    "rr": "A",
    "server": "192.168.x.x",
    "description": "Grafana"
  }
}

Update Host Override

POST /api/unbound/settings/setHostOverride/{uuid}
Authorization: Basic (API key:secret)
Content-Type: application/json

Delete Host Override

POST /api/unbound/settings/delHostOverride/{uuid}
Authorization: Basic (API key:secret)

Reconfigure Unbound

POST /api/unbound/service/reconfigure
Authorization: Basic (API key:secret)

Applies pending DNS configuration changes.

Use Cases

Internal Service Resolution

- hostname: "nextcloud"
  domain: "localdomain"
  server: "192.168.x.x"
  description: "Nextcloud file sharing"

Management Interface Separation

# Service VLAN
- hostname: "grafana"
  domain: "localdomain"
  server: "192.168.x.x"
  description: "Grafana (service)"

# Management VLAN
- hostname: "grafana-mgmt"
  domain: "localdomain"
  server: "192.168.x.x"
  description: "Grafana (management)"

Block Malicious TLDs

- hostname: "*"
  domain: "zip"
  server: "192.168.x.x"
  description: "Block .zip TLD (phishing)"

- hostname: "*"
  domain: "mov"
  server: "192.168.x.x"
  description: "Block .mov TLD (phishing)"

MX Record

- hostname: "mail"
  domain: "localdomain"
  rr: "MX"
  mxprio: "10"
  mx: "server.homelab"
  description: "Internal mail server"

Security Considerations

  • API Credentials: Stored in Ansible Vault
  • HTTPS: Uses SSL/TLS for API calls
  • Basic Auth: API key/secret authentication
  • Certificate Validation: Enabled by default
  • Domain Blocking: Can block phishing TLDs

Troubleshooting

”Authentication failed” errors

Cause: Invalid API key/secret

Solution: Verify credentials in vault and test API manually.

Overrides not resolving

Cause: Unbound not reconfigured

Solution: Check role output for reconfigure task execution.

DNS not updating

Cause: Client DNS cache

Solution: Flush DNS cache on client (ipconfig /flushdns or systemd-resolve --flush-caches)

Best Practices

  1. Consistent naming: Use clear hostname conventions
  2. Separate management: Create -mgmt entries for management VLANs
  3. Document overrides: Use descriptive descriptions
  4. Block malicious TLDs: Add wildcard blocks for known-bad TLDs
  5. Version control: Store overrides in git
  6. Review before running: Removing overrides from vars deletes them

This role works well with:

  • opnsense_firewall: Configure firewall rules for DNS traffic
  • opnsense_aliases: Create aliases for DNS servers

License

MIT

Author

Created for homelab infrastructure management.