index
Type
External
Status
Published
Created
Jul 18, 2026
Updated
Jul 18, 2026

Data Model & Integration#

This guide explains how to work with opnDossier's data model for custom integrations, scripting, and automation.

Overview#

opnDossier parses OPNsense and pfSense configuration files (config.xml) and converts them to structured data formats:

  • Markdown - Human-readable documentation
  • JSON - Machine-readable, ideal for scripting with jq
  • YAML - Configuration-friendly, works with yq

The JSON/YAML export uses the CommonDevice model -- a platform-agnostic representation that normalizes XML quirks (presence-based booleans, pointer types, map-keyed collections) into clean types suitable for scripting and integration. Both OPNsense and pfSense configs are normalized to identical CommonDevice structures, enabling unified scripting regardless of device type. opnDossier auto-detects the device type from the XML root element (<opnsense> or <pfsense>).

Quick Start#

Export Configuration#

# Export to JSON for scripting
opndossier convert config.xml --format json -o config.json

# Export to YAML for configuration management
opndossier convert config.xml --format yaml -o config.yaml

# Export to Markdown for documentation
opndossier convert config.xml --format markdown -o config.md

Query Data with jq#

# Get hostname
jq '.system.hostname' config.json

# List all interface names
jq '[.interfaces[].name]' config.json

# Get all enabled firewall rules
jq '.firewallRules[] | select(.disabled | not)' config.json

# Count rules by interface
jq '[.firewallRules[] | .interfaces[]] | group_by(.) | map({interface: .[0], count: length})' config.json

Query Data with yq#

# Get system domain
yq '.system.domain' config.yaml

# List DHCP scopes
yq '[.dhcp[].interface]' config.yaml

Data Model Structure#

The CommonDevice model organizes configuration into logical sections:

CommonDevice (root)
├── system # Hostname, domain, SSH, WebGUI, firmware
├── interfaces[] # Network interface configurations (flat array)
├── vlans[] # VLAN configurations
├── firewallRules[] # Normalized firewall filter rules
├── nat # NAT rules (outboundRules, inboundRules)
├── dhcp[] # DHCP server scopes
├── dns # DNS resolver (unbound, dnsMasq)
├── vpn # VPN (openVpn, wireGuard, ipsec)
├── routing # Gateways, gateway groups, static routes
├── users[] # System user accounts
├── groups[] # System groups
├── certificates[] # TLS/SSL certificates
├── cas[] # Certificate authorities
└── ... # Additional services (syslog, ids, snmp, etc.)

Documentation#

Common Use Cases#

Security Auditing#

# Find rules allowing any source
jq '.firewallRules[] | select(.source.address == "any") | {
  interfaces, description, destination
}' config.json

# List NAT port forwards
jq '.nat.inboundRules[]' config.json

# Check SSH configuration
jq '.system.ssh' config.json

Network Documentation#

# Get all VLANs
jq '.vlans[] | {tag, vlanIf, description}' config.json

# List gateways
jq '.routing.gateways[] | {name, interface, address}' config.json

# Get interface IP addresses
jq '.interfaces[] | {name, ipAddress, subnet}' config.json

Configuration Comparison#

Use the built-in diff command for content-aware, security-scored comparison:

# Compare two configs with security impact scoring
opndossier diff old-config.xml new-config.xml

# Generate JSON diff for automation
opndossier diff old-config.xml new-config.xml -f json

# Compare only firewall rules
opndossier diff old-config.xml new-config.xml --section firewall

Integration Examples#

Ansible#

  - name: Extract OPNsense configuration
    command: opndossier convert {{ config_file }} --format json
    register: opnsense_config

  - name: Parse configuration
    set_fact:
      firewall_rules: '{{ (opnsense_config.stdout | from_json).firewallRules }}'

Python#

import json
import subprocess

result = subprocess.run(
    ["opndossier", "convert", "config.xml", "--format", "json"],
    capture_output=True,
    text=True,
)
config = json.loads(result.stdout)
print(f"Hostname: {config['system']['hostname']}")
print(f"Interfaces: {[iface['name'] for iface in config['interfaces']]}")

Shell Scripting#

#!/bin/bash
CONFIG=$(opndossier convert config.xml --format json)

HOSTNAME=$(echo "$CONFIG" | jq -r '.system.hostname')
RULE_COUNT=$(echo "$CONFIG" | jq '.firewallRules | length')

echo "Firewall: $HOSTNAME"
echo "Rules: $RULE_COUNT"

Programmatic Generation#

opnDossier uses programmatic markdown generation rather than text templates. If you need custom report formats, you can:

  1. Use JSON/YAML exports - Process with your preferred tools
  2. Extend the generator - Add custom report types in Go
  3. Post-process output - Transform markdown with pandoc or similar

See the Architecture Documentation for details on the generator system.

Regenerating Documentation#

The model reference is auto-generated from Go types:

just generate-docs

This ensures documentation stays in sync with the codebase.