Skip to content

Repository files navigation

MudParser

Python 3.10+License: MITRFC 8520RFC 9761TestsCode style: ruff

A production-grade Python library for parsing, validating, and exporting Manufacturer Usage Description (MUD) profiles as defined in RFC 8520 with full support for RFC 9761 TLS/DTLS profiles.

MUD profiles allow IoT device manufacturers to formally describe the network behavior their devices require, enabling network administrators to automatically configure access control policies and significantly reduce the attack surface of IoT deployments.


Table of Contents


What is MUD?

Manufacturer Usage Description (MUD) is an IETF standard (RFC 8520) that provides a formal way for IoT device manufacturers to describe the intended network behavior of their devices.

The Problem MUD Solves

IoT devices often have well-defined, limited network communication patterns. However, without formal descriptions:

  • Network administrators don't know what traffic is legitimate
  • Devices may be over-permissioned on the network
  • Compromised devices can be used to attack other systems
  • Manual firewall configuration is error-prone and time-consuming

How MUD Works

┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ IoT Device │────>│ MUD Manager │────>│ Firewall │
│ (sends URL)│ │ (fetches) │ │ (enforces) │
└─────────────┘ └──────┬──────┘ └─────────────┘
│
v
┌─────────────┐
│ MUD File │
│ (on web) │
└─────────────┘
  1. Device Announcement: IoT device announces its MUD URL (via DHCP, LLDP, or 802.1X)
  2. Profile Fetch: MUD manager fetches the MUD file from the URL
  3. Policy Generation: MUD file is parsed and converted to firewall rules
  4. Enforcement: Rules are applied to restrict device communication

MUD File Structure

A MUD file is a JSON document containing:

  • Metadata: Device info, update timestamps, support status
  • Access Control Lists (ACLs): Network communication rules
  • Policy References: Which ACLs apply to inbound/outbound traffic

Features

Core Capabilities

FeatureDescription
RFC 8520 CompliantFull implementation of the MUD specification
RFC 9761 SupportTLS/DTLS profile extensions for secure communications
Modern PythonBuilt for Python 3.10+ with type hints and Pydantic v2
Multiple Input SourcesParse from files, strings, URLs, or dictionaries
Async SupportAsynchronous URL fetching for high-performance applications

Validation

FeatureDescription
Schema ValidationValidates against RFC 8520 JSON schema
Semantic ValidationCross-reference checking, constraint validation
Severity LevelsErrors, warnings, and informational messages
Detailed ReportingPath-specific error messages with context

Export Formats

FormatPlatformUse Case
iptablesLinuxTraditional Linux firewall
nftablesLinuxModern Linux firewall (netfilter)
Cisco ACLCisco IOSEnterprise routers and switches
pfSenseBSD/pfSenseBSD-based firewalls
JSONAnyRe-serialization, APIs
YAMLAnyHuman-readable configuration

User Interfaces

InterfaceDescription
Python APIFull-featured library for programmatic access
CLIRich command-line interface with syntax highlighting
Web DemoInteractive Streamlit application

Installation

Requirements

  • Python 3.10 or higher (uses modern features like match/case, union types)
  • pip package manager

Basic Installation

pip install mudparser

Installation with Extras

# Development tools (pytest, mypy, ruff)
pip install mudparser[dev]
# Documentation tools (mkdocs, mkdocstrings)
pip install mudparser[docs]
# Demo application (streamlit)
pip install mudparser[demo]
# All extras
pip install mudparser[all]

Installation from Source

# Clone the repository
git clone https://github.com/elmiomar/mudparser.git
cd mudparser
# Install in development mode
pip install -e .# Or with all extras
pip install -e ".[all]"

Verify Installation

# Check version
mudparser --version
# Run a quick test
mudparser --help

Quick Start

30-Second Example

frommudparserimportMUDParser# Parse a MUD fileparser=MUDParser.from_file("device.mud.json")
# Get device infoprint(f"Device: {parser.mud.systeminfo}")
print(f"Rules: {parser.get_summary()['total_rules']}")
# Generate firewall rulesrules=parser.export.to_iptables(device_ip="192.168.1.100")
print(rules)

CLI Quick Start

# View profile information
mudparser info device.mud.json
# Validate a profile
mudparser validate device.mud.json
# Export to iptables
mudparser export device.mud.json -f iptables -d 192.168.1.100

Detailed Usage Guide

Parsing MUD Files

From a Local File

frommudparserimportMUDParser# Parse from file path (string or Path object)parser=MUDParser.from_file("path/to/device.mud.json")
# With pathlibfrompathlibimportPathparser=MUDParser.from_file(Path("data") /"device.mud.json")

From a JSON String

frommudparserimportMUDParserjson_content='''{ "ietf-mud:mud": { "mud-version": 1, "mud-url": "https://example.com/device.mud.json", "last-update": "2024-01-15T00:00:00Z", "cache-validity": 48, "is-supported": true, "systeminfo": "My IoT Device", "from-device-policy": { ... }, "to-device-policy": { ... } }, "ietf-access-control-list:access-lists": { ... }}'''parser=MUDParser.from_string(json_content, source="inline")

From a URL

frommudparserimportMUDParser# Synchronous fetchparser=MUDParser.from_url("https://iotanalytics.unsw.edu.au/mud/amazonEchoMud.json")
# Asynchronous fetch (for high-performance applications)importasyncioasyncdeffetch_profile():
parser=awaitMUDParser.from_url_async(
"https://iotanalytics.unsw.edu.au/mud/amazonEchoMud.json"
)
returnparserparser=asyncio.run(fetch_profile())

From a Dictionary

frommudparserimportMUDParserdata= {
"ietf-mud:mud": {
"mud-version": 1,
"mud-url": "https://example.com/device.mud.json",
"last-update": "2024-01-15T00:00:00Z",
"cache-validity": 48,
"is-supported": True,
"systeminfo": "My IoT Device",
"from-device-policy": {
"access-lists": {
"access-list": [{"name": "from-device-acl"}]
}
},
"to-device-policy": {
"access-lists": {
"access-list": [{"name": "to-device-acl"}]
}
}
},
"ietf-access-control-list:access-lists": {
"acl": [
# ... ACL definitions
]
}
}
parser=MUDParser.from_dict(data, source="programmatic")

Accessing Profile Data

MUD Container (Metadata)

frommudparserimportMUDParserparser=MUDParser.from_file("device.mud.json")
# Core metadata (always present)print(f"MUD Version: {parser.mud.mud_version}") # 1print(f"MUD URL: {parser.mud.mud_url}") # https://...print(f"Last Update: {parser.mud.last_update}") # datetime objectprint(f"Cache Validity: {parser.mud.cache_validity}h") # Hours (1-168)print(f"Is Supported: {parser.mud.is_supported}") # True/Falseprint(f"System Info: {parser.mud.systeminfo}") # Device description# Optional metadataprint(f"Manufacturer: {parser.mud.mfg_name}") # May be Noneprint(f"Model: {parser.mud.model_name}") # May be Noneprint(f"Firmware: {parser.mud.firmware_rev}") # May be Noneprint(f"Software: {parser.mud.software_rev}") # May be Noneprint(f"Documentation: {parser.mud.documentation}") # URL or Noneprint(f"MUD Signature: {parser.mud.mud_signature}") # Signature URLprint(f"Extensions: {parser.mud.extensions}") # List of extensions

Profile Summary

summary=parser.get_summary()
print(f"URL: {summary['url']}")
print(f"Version: {summary['version']}")
print(f"Device: {summary['systeminfo']}")
print(f"Last Update: {summary['last_update']}")
print(f"Cache Validity: {summary['cache_validity_hours']} hours")
print(f"Supported: {summary['is_supported']}")
print(f"Manufacturer: {summary['manufacturer']}")
print(f"Model: {summary['model']}")
# Rule countsprint(f"Total ACLs: {summary['total_acls']}")
print(f"From-Device ACLs: {summary['from_device_acls']}")
print(f"To-Device ACLs: {summary['to_device_acls']}")
print(f"Total Rules: {summary['total_rules']}")
print(f"From-Device Rules: {summary['from_device_rules']}")
print(f"To-Device Rules: {summary['to_device_rules']}")
# Network resourcesprint(f"DNS Names: {summary['dns_names']}")
print(f"TCP Ports: {summary['ports']['tcp']}")
print(f"UDP Ports: {summary['ports']['udp']}")

Extracting Network Resources

# Get all DNS names referenced in the profiledns_names=parser.get_dns_names()
print("DNS Names:")
fornameinsorted(dns_names):
print(f" - {name}")
# Get all ports usedports=parser.get_ports()
print(f"TCP Ports: {sorted(ports['tcp'])}")
print(f"UDP Ports: {sorted(ports['udp'])}")
# Get all ACE entries with directionfordirection, entryinparser.get_all_entries():
print(f"[{direction}] {entry.name}: {entry.get_description(direction)}")

Working with ACLs and ACEs

Iterating Over ACLs

# Get all ACLsforaclinparser.profile.acls.acl:
print(f"\nACL: {acl.name}")
print(f" Type: {acl.acl_type.value}") # ipv4-acl-type, ipv6-acl-type, etc.print(f" Rules: {len(acl)}")
# Get ACLs by directionprint("\n=== FROM-DEVICE (Outbound) ===")
foraclinparser.get_from_device_acls():
print(f" {acl.name}: {len(acl)} rules")
print("\n=== TO-DEVICE (Inbound) ===")
foraclinparser.get_to_device_acls():
print(f" {acl.name}: {len(acl)} rules")

Working with ACL Entries (ACEs)

foraclinparser.profile.acls.acl:
print(f"\nACL: {acl.name}")
forentryinacl.entries:
# Basic infoprint(f" Rule: {entry.name}")
# Actionifentry.is_accept():
print(" Action: ALLOW")
else:
print(" Action: DENY")
# Get human-readable descriptiondesc=entry.get_description(direction="from")
print(f" Description: {desc}")
# Access match conditionsmatches=entry.matches# Protocol informationprotocol=matches.get_protocol()
print(f" Protocol: {protocol}")
# DNS names in this ruledns_names=matches.get_dns_names()
ifdns_names:
print(f" DNS Names: {dns_names}")

Accessing Match Details

foraclinparser.profile.acls.acl:
forentryinacl.entries:
matches=entry.matches# IPv4 matchesifmatches.ipv4:
print(f" IPv4 Protocol: {matches.ipv4.protocol}")
ifmatches.ipv4.src_network:
print(f" Source Network: {matches.ipv4.src_network}")
ifmatches.ipv4.dst_network:
print(f" Dest Network: {matches.ipv4.dst_network}")
ifmatches.ipv4.src_dnsname:
print(f" Source DNS: {matches.ipv4.src_dnsname}")
ifmatches.ipv4.dst_dnsname:
print(f" Dest DNS: {matches.ipv4.dst_dnsname}")
# IPv6 matchesifmatches.ipv6:
print(f" IPv6 Protocol: {matches.ipv6.protocol}")
ifmatches.ipv6.dst_network:
print(f" Dest Network: {matches.ipv6.dst_network}")
# TCP matchesifmatches.tcp:
ifmatches.tcp.src_port:
print(f" TCP Source Port: {matches.tcp.src_port}")
ifmatches.tcp.dst_port:
print(f" TCP Dest Port: {matches.tcp.dst_port}")
ifmatches.tcp.direction_initiated:
print(f" Direction: {matches.tcp.direction_initiated.value}")
# UDP matchesifmatches.udp:
ifmatches.udp.src_port:
print(f" UDP Source Port: {matches.udp.src_port}")
ifmatches.udp.dst_port:
print(f" UDP Dest Port: {matches.udp.dst_port}")
# ICMP matchesifmatches.icmp:
print(f" ICMP Type: {matches.icmp.type}, Code: {matches.icmp.code}")
# Ethernet matchesifmatches.eth:
ifmatches.eth.src_mac:
print(f" Source MAC: {matches.eth.src_mac}")
ifmatches.eth.dst_mac:
print(f" Dest MAC: {matches.eth.dst_mac}")
ifmatches.eth.ethertype:
print(f" Ethertype: {matches.eth.ethertype}")
# MUD-specific matchesifmatches.mud:
match_type=matches.mud.get_match_type()
print(f" MUD Match Type: {match_type}")
ifmatches.mud.manufacturer:
print(f" Manufacturer: {matches.mud.manufacturer}")
ifmatches.mud.controller:
print(f" Controller: {matches.mud.controller}")
ifmatches.mud.local_networks:
print(" Local Networks: Yes")
ifmatches.mud.same_manufacturer:
print(" Same Manufacturer: Yes")

Validation

Basic Validation

frommudparserimportMUDParserparser=MUDParser.from_file("device.mud.json")
# Simple validation (returns list of error strings)errors=parser.validate()
iferrors:
print("Validation FAILED:")
forerrorinerrors:
print(f" - {error}")
else:
print("Validation PASSED!")

Detailed Validation

frommudparserimportMUDParserfrommudparser.validatorimportMUDValidator, ValidationSeverityparser=MUDParser.from_file("device.mud.json")
validator=MUDValidator()
# Full validation with detailed resultsresult=validator.validate(parser.profile)
# Summaryprint(f"Valid: {result.is_valid}")
print(f"Total Issues: {len(result.issues)}")
print(f"Errors: {result.error_count}")
print(f"Warnings: {result.warning_count}")
# Detailed issuesforissueinresult.issues:
severity_icon= {
ValidationSeverity.ERROR: "ERROR",
ValidationSeverity.WARNING: "WARNING",
ValidationSeverity.INFO: "INFO"
}[issue.severity]
print(f"[{severity_icon}] {issue.message}")
ifissue.path:
print(f" Path: {issue.path}")
# Get only errorsforerrorinresult.errors:
print(f"Error: {error.message}")
# Get only warningsforwarninginresult.warnings:
print(f"Warning: {warning.message}")

Strict Mode Validation

# Strict mode treats warnings as errorsresult=validator.validate(parser.profile, strict=True)
ifnotresult.is_valid:
print("Profile has errors or warnings in strict mode")

Convenience Functions

frommudparser.validatorimportvalidate_profile, validate_json# Validate a MUDProfile objectresult=validate_profile(parser.profile)
# Validate raw JSON dataresult=validate_json({
"ietf-mud:mud": { ... },
"ietf-access-control-list:access-lists": { ... }
})

Exporting to Firewall Rules

iptables Export

frommudparserimportMUDParserparser=MUDParser.from_file("device.mud.json")
# Basic exportrules=parser.export.to_iptables(device_ip="192.168.1.100")
# With optionsrules=parser.export.to_iptables(
device_ip="192.168.1.100",
chain_prefix="IOT", # Custom chain prefix (default: MUD)include_comments=True, # Include rule comments (default: True)
)
print(rules)
# Save to filewithopen("firewall_rules.sh", "w") asf:
f.write(rules)

Example iptables output:

#!/bin/bash# IPTables rules generated from MUD profile# Device: Amazon Echo# MUD URL: https://example.com/echo.mud.json# Device IP: 192.168.1.100# Create custom chains
iptables -N IOT_FROM_ECHO 2>/dev/null || iptables -F IOT_FROM_ECHO
iptables -N IOT_TO_ECHO 2>/dev/null || iptables -F IOT_TO_ECHO
# Jump to custom chains
iptables -A FORWARD -s 192.168.1.100 -j IOT_FROM_ECHO
iptables -A FORWARD -d 192.168.1.100 -j IOT_TO_ECHO
# FROM-DEVICE rules (outbound traffic from IoT device)
iptables -A IOT_FROM_ECHO -s 192.168.1.100 -p tcp -d api.amazon.com --dport 443 \
-m state --state NEW,ESTABLISHED -m comment --comment "allow-https-api" -j ACCEPT
# Default deny
iptables -A IOT_FROM_ECHO -j DROP
iptables -A IOT_TO_ECHO -j DROP

nftables Export

rules=parser.export.to_nftables(
device_ip="192.168.1.100",
table_name="iot_devices", # Custom table name (default: mud_filter)
)
# Save to filewithopen("rules.nft", "w") asf:
f.write(rules)
# Apply with: nft -f rules.nft

Example nftables output:

#!/usr/sbin/nft -f
# nftables rules generated from MUD profile
# Device: Amazon Echo
table inet iot_devices {
chain from_echo {
type filter hook forward priority 0; policy drop;
ip saddr 192.168.1.100 tcp dport 443 ip daddr api.amazon.com accept
ip saddr 192.168.1.100 udp dport 53 accept
}
chain to_echo {
type filter hook forward priority 0; policy drop;
ip daddr 192.168.1.100 tcp sport 443 ip saddr api.amazon.com accept
}
}

Cisco ACL Export

rules=parser.export.to_cisco_acl(
acl_number_start=100, # Starting ACL number (default: 100)include_remarks=True, # Include remark statements (default: True)
)
print(rules)

Example Cisco ACL output:

! Cisco ACL generated from MUD profile
! Device: Amazon Echo
! MUD URL: https://example.com/echo.mud.json
! FROM-DEVICE ACL (outbound from IoT device)
access-list 100 remark MUD profile: Amazon Echo - from-device
access-list 100 permit tcp any host api.amazon.com eq 443
access-list 100 permit udp any any eq 53
access-list 100 deny ip any any
! TO-DEVICE ACL (inbound to IoT device)
access-list 101 remark MUD profile: Amazon Echo - to-device
access-list 101 permit tcp host api.amazon.com eq 443 any
access-list 101 deny ip any any

pfSense XML Export

rules=parser.export.to_pfsense(
device_ip="192.168.1.100",
interface="lan", # Interface name (default: lan)
)
# Save to file for importwithopen("pfsense_rules.xml", "w") asf:
f.write(rules)

JSON/YAML Export

# JSON export (re-serialized MUD profile)json_output=parser.export.to_json(indent=2)
# YAML export (human-readable)yaml_output=parser.export.to_yaml()

Generic Export Interface

frommudparser.exportersimportExportFormat# Export using format enumoutput=parser.export.export(
format=ExportFormat.IPTABLES,
device_ip="192.168.1.100"
)
# Export using format stringoutput=parser.export.export(
format="nftables",
device_ip="192.168.1.100"
)
# Available formatsforfmtinExportFormat:
print(f" - {fmt.value}")

Export Summary

# Get export summary before generating rulessummary=parser.export.get_summary()
print(f"Device: {summary['device_info']}")
print(f"From-Device Rules: {summary['from_device_rules']}")
print(f"To-Device Rules: {summary['to_device_rules']}")
print(f"Total Rules: {summary['total_rules']}")
print(f"DNS Names: {summary['dns_names']}")
print(f"Ports: {summary['ports']}")
print(f"Supported Formats: {summary['supported_formats']}")

Command Line Interface

The mudparser CLI provides a rich, user-friendly interface for working with MUD profiles.

Available Commands

CommandDescription
validateValidate a MUD profile against RFC 8520
infoDisplay profile information and summary
rulesShow access control rules in human-readable format
exportExport to firewall rule format
fetchFetch a MUD profile from URL
diffCompare two MUD profiles
demoLaunch interactive Streamlit demo

Command: validate

Validate a MUD profile for RFC compliance.

# Basic validation
mudparser validate device.mud.json
# Verbose output (show all issues including warnings)
mudparser validate device.mud.json --verbose
# Strict mode (treat warnings as errors)
mudparser validate device.mud.json --strict
# JSON output (for scripting)
mudparser validate device.mud.json --json

Command: info

Display profile information and summary.

# Show profile info
mudparser info device.mud.json
# JSON output
mudparser info device.mud.json --json

Example output:

╭──────────────────────────────── MUD Profile ─────────────────────────────────╮
│ Amazon Echo │
│ URL: https://amazonecho.com/amazonecho │
╰──────────────────────────────────────────────────────────────────────────────╯
Profile Metadata
┏━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ Property ┃ Value ┃
┡━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
│ MUD Version │ 1 │
│ Last Update │ 2024-01-15T00:00:00+00:00 │
│ Cache Validity │ 48 hours │
│ Supported │ Yes │
└────────────────┴──────────────────────────────────┘
Access Control Summary
┏━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━┳━━━━━━━┓
┃ Direction ┃ ACLs ┃ Rules ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━╇━━━━━━━┩
│ From Device (Outbound) │ 2 │ 15 │
│ To Device (Inbound) │ 1 │ 8 │
│ Total │ 3 │ 23 │
└────────────────────────┴──────┴───────┘
Referenced DNS Names:
- api.amazonalexa.com
- device-metrics-us.amazon.com
- softwareupdates.amazon.com
Referenced Ports:
TCP: 80, 443
UDP: 53, 123

Command: rules

Show access control rules in human-readable format.

# Show all rules
mudparser rules device.mud.json
# Show only from-device (outbound) rules
mudparser rules device.mud.json --direction from
# Show only to-device (inbound) rules
mudparser rules device.mud.json --direction to
# JSON output
mudparser rules device.mud.json --json

Command: export

Export to firewall rule format.

# Export to iptables
mudparser export device.mud.json -f iptables -d 192.168.1.100
# Export to nftables with custom table name
mudparser export device.mud.json -f nftables -d 192.168.1.100
# Export to Cisco ACL
mudparser export device.mud.json -f cisco
# Export to pfSense
mudparser export device.mud.json -f pfsense -d 192.168.1.100
# Save to file
mudparser export device.mud.json -f iptables -d 192.168.1.100 -o rules.sh
# Available formats: iptables, nftables, cisco, pfsense, json, yaml

Command: fetch

Fetch a MUD profile from URL.

# Fetch and display info
mudparser fetch https://iotanalytics.unsw.edu.au/mud/amazonEchoMud.json
# Fetch and save to file
mudparser fetch https://example.com/device.mud.json -o device.mud.json
# Fetch and validate
mudparser fetch https://example.com/device.mud.json --validate

Command: diff

Compare two MUD profiles.

# Compare two profiles
mudparser diff old.mud.json new.mud.json
# Show only changes in rules
mudparser diff old.mud.json new.mud.json --rules-only

Command: demo

Launch the interactive Streamlit web demo.

# Launch on default port (8501)
mudparser demo
# Launch on custom port
mudparser demo --port 8080

Global Options

# Show version
mudparser --version
# Show help
mudparser --help
# Show help for a specific command
mudparser validate --help

Interactive Web Demo

MudParser includes an interactive Streamlit web application for exploring MUD profiles.

Launching the Demo

# Via CLI
mudparser demo
# Directly with Streamlit
streamlit run demo/streamlit_app.py
# On a custom port
mudparser demo --port 8080

Demo Features

  1. Load Profiles: Upload files, paste JSON, or use built-in samples
  2. Overview Tab: Device info, metadata, and rule summaries
  3. Rules Tab: Interactive ACL/ACE browser with filtering
  4. Validation Tab: Detailed validation results with severity levels
  5. Export Tab: Generate and download firewall rules
  6. Raw JSON Tab: View and download the original profile

Screenshots

The demo provides:

  • Visual representation of ACLs and rules
  • Color-coded action indicators (ALLOW/DENY)
  • Interactive export with format selection
  • Real-time validation feedback

RFC Compliance

RFC 8520 - Manufacturer Usage Description Specification

MudParser fully implements RFC 8520, including:

MUD Container

FieldStatusDescription
mud-versionMUD specification version
mud-urlURL of this MUD file
mud-signatureURL of PKCS#7 signature
last-updateLast modification timestamp
cache-validityHours this file can be cached (1-168)
is-supportedWhether device is still supported
systeminfoDevice description (max 60 chars)
mfg-nameManufacturer name
model-nameModel name
firmware-revFirmware revision
software-revSoftware revision
documentationDocumentation URL
extensionsList of MUD extensions
from-device-policyOutbound traffic policy
to-device-policyInbound traffic policy

Access Control Lists (RFC 8519)

FeatureStatusDescription
IPv4 ACLIPv4 access control list
IPv6 ACLIPv6 access control list
Ethernet ACLLayer 2 access control list

Match Types

Match TypeStatusFields
IPv4protocol, src/dst network, src/dst DNS name, DSCP, ECN, length, TTL, flags
IPv6protocol, src/dst network, src/dst DNS name, DSCP, ECN, flow-label, length, hop-limit
TCPsrc/dst port (with operators), flags, direction-initiated
UDPsrc/dst port (with operators)
ICMPtype, code
ICMPv6type, code
Ethernetsrc/dst MAC address, ethertype

MUD-Specific Extensions

ExtensionStatusDescription
manufacturerMatch by manufacturer domain
same-manufacturerMatch devices from same manufacturer
modelMatch by model URI
local-networksMatch local network traffic
controllerMatch MUD controller (dns, ntp, gateway)
my-controllerMatch custom controller URIs

Port Operators

OperatorStatusDescription
eqEquals
ltLess than
gtGreater than
neqNot equal
rangePort range

RFC 9761 - MUD for TLS/DTLS Profiles

MudParser supports RFC 9761 TLS profile extensions:

FeatureStatusDescription
TLS version constraintsMinimum/maximum TLS versions
Cipher suite restrictionsAllowed/forbidden cipher suites
SPKI pin setsCertificate pinning with SPKI hashes
Client authenticationClient certificate requirements
Server authenticationServer certificate requirements
DTLS profilesDTLS-specific settings

Sample MUD Profiles

MudParser includes several sample MUD profiles for testing:

FileDeviceDescription
data/amazon_echo.jsonAmazon EchoFull profile with many rules
data/amazon_echo_short.jsonAmazon EchoSimplified version
data/ring_doorbell.jsonRing DoorbellSmart doorbell profile
data/philips_hue_bulb.jsonPhilips HueSmart light bulb profile
data/nest_smoke_sensor.jsonNest ProtectSmoke/CO detector profile

Testing with Sample Profiles

# View Amazon Echo profile
mudparser info data/amazon_echo_short.json
# Validate Ring Doorbell
mudparser validate data/ring_doorbell.json
# Export Philips Hue to iptables
mudparser export data/philips_hue_bulb.json -f iptables -d 192.168.1.100
# Compare profiles
mudparser diff data/amazon_echo_short.json data/amazon_echo.json

Online MUD Profile Sources


API Reference

Main Classes

MUDParser

The main entry point for parsing MUD files.

classMUDParser:
# Class methods for parsing@classmethoddeffrom_file(cls, path: str|Path, source: str=None) ->MUDParser
@classmethoddeffrom_string(cls, content: str, source: str=None) ->MUDParser@classmethoddeffrom_url(cls, url: str, timeout: float=30.0) ->MUDParser
@classmethodasyncdeffrom_url_async(cls, url: str, timeout: float=30.0) ->MUDParser@classmethoddeffrom_dict(cls, data: dict, source: str=None) ->MUDParser# Properties
@propertydefmud(self) ->MUD# MUD container
@propertydefprofile(self) ->MUDProfile# Full profile
@propertydefexport(self) ->MUDExporter# Exporter interface# Methodsdefvalidate(self, strict: bool=False) ->list[str]
defget_summary(self) ->dict[str, Any]
defget_acl(self, name: str) ->AccessControlList|Nonedefget_from_device_acls(self) ->list[AccessControlList]
defget_to_device_acls(self) ->list[AccessControlList]
defget_dns_names(self) ->set[str]
defget_ports(self) ->dict[str, set[int]]
defget_all_entries(self) ->Iterator[tuple[str, AccessControlEntry]]
defto_dict(self) ->dictdefto_json(self, indent: int=None) ->str

MUDValidator

Validation engine with detailed results.

classMUDValidator:
defvalidate(
self,
profile: MUDProfile,
strict: bool=False
) ->ValidationResultclassValidationResult:
is_valid: boolissues: list[ValidationIssue]
error_count: intwarning_count: interrors: list[ValidationIssue] # Propertywarnings: list[ValidationIssue] # Propertydefto_dict(self) ->dictclassValidationIssue:
severity: ValidationSeveritymessage: strpath: str|NoneclassValidationSeverity(Enum):
ERROR="error"WARNING="warning"INFO="info"

MUDExporter

Export interface for generating firewall rules.

classMUDExporter:
defto_json(self, indent: int=None) ->strdefto_yaml(self) ->strdefto_iptables(
self,
device_ip: str,
chain_prefix: str="MUD",
include_comments: bool=True
) ->strdefto_nftables(
self,
device_ip: str,
table_name: str="mud_filter"
) ->strdefto_cisco_acl(
self,
acl_number_start: int=100,
include_remarks: bool=True
) ->strdefto_pfsense(
self,
device_ip: str,
interface: str="lan"
) ->strdefexport(
self,
format: str|ExportFormat,
**kwargs
) ->strdefget_summary(self) ->dict[str, Any]

Data Models

MUD (Container)

classMUD(BaseModel):
mud_version: intmud_url: HttpUrlmud_signature: HttpUrl|Nonelast_update: datetimecache_validity: int# 1-168 hoursis_supported: boolsysteminfo: str|Nonemfg_name: str|Nonemodel_name: str|Nonefirmware_rev: str|Nonesoftware_rev: str|Nonedocumentation: HttpUrl|Noneextensions: list[str]
from_device_policy: PolicyReferenceto_device_policy: PolicyReference

AccessControlList

classAccessControlList(BaseModel):
name: stracl_type: ACLTypeaces: ACEs@propertydefentries(self) ->list[AccessControlEntry]
defis_ipv4(self) ->booldefis_ipv6(self) ->booldefis_ethernet(self) ->booldefget_accept_rules(self) ->list[AccessControlEntry]
defget_deny_rules(self) ->list[AccessControlEntry]

AccessControlEntry

classAccessControlEntry(BaseModel):
name: strmatches: ACEMatchesactions: ACEActionsdefis_accept(self) ->booldefis_deny(self) ->booldefget_description(self, direction: str="from") ->str

Exceptions

classMUDParserError(Exception):
"""Base exception for all MUD parser errors."""classMUDFileNotFoundError(MUDParserError):
"""Raised when a MUD file is not found."""file_path: strclassMUDSchemaError(MUDParserError):
"""Raised when JSON structure is invalid."""message: strclassMUDValidationError(MUDParserError):
"""Raised when validation fails."""message: strerrors: list[str]
classMUDNetworkError(MUDParserError):
"""Raised when network operations fail."""message: strurl: str|Nonestatus_code: int|None

Architecture

Project Structure

mudparser/
├── pyproject.toml # Modern Python packaging
├── README.md # This file
├── CHANGELOG.md # Version history
├── LICENSE # MIT License
│
├── src/mudparser/ # Main package (src layout)
│ ├── __init__.py # Package exports
│ ├── __main__.py # CLI entry point
│ ├── parser.py # MUDParser class
│ ├── validator.py # Validation engine
│ ├── cli.py # Typer CLI application
│ ├── exceptions.py # Custom exceptions
│ │
│ ├── models/ # Pydantic data models
│ │ ├── __init__.py
│ │ ├── mud.py # MUD container model
│ │ ├── acl.py # ACL models
│ │ ├── ace.py # ACE models
│ │ ├── matches.py # Match condition models
│ │ └── tls.py # RFC 9761 TLS models
│ │
│ └── exporters/ # Export implementations
│ ├── __init__.py
│ ├── base.py # MUDExporter class
│ ├── iptables.py # iptables generator
│ ├── nftables.py # nftables generator
│ ├── cisco.py # Cisco ACL generator
│ └── pfsense.py # pfSense XML generator
│
├── tests/ # Test suite
│ ├── conftest.py # Pytest fixtures
│ ├── test_parser.py
│ ├── test_models.py
│ ├── test_validator.py
│ └── test_exporter.py
│
├── docs/ # MkDocs documentation
│ ├── index.md
│ ├── installation.md
│ ├── quickstart.md
│ └── ...
│
├── examples/ # Example scripts
│ ├── basic_usage.py
│ └── export_to_firewall.py
│
├── demo/ # Streamlit demo app
│ └── streamlit_app.py
│
└── data/ # Sample MUD profiles
├── amazon_echo.json
├── ring_doorbell.json
└── ...

Design Principles

  1. Type Safety: Full type hints with Pydantic v2 for runtime validation
  2. RFC Compliance: Strict adherence to RFC 8520 and RFC 9761
  3. Extensibility: Easy to add new export formats
  4. Testability: High test coverage with comprehensive fixtures
  5. User Experience: Rich CLI with helpful error messages

Dependencies

Runtime

PackagePurpose
pydantic>=2.0Data validation and models
httpx>=0.25HTTP client for URL fetching
pyyaml>=6.0YAML export
typer>=0.9CLI framework
rich>=13.0Terminal formatting

Development

PackagePurpose
pytest>=7.0Testing framework
pytest-cov>=4.0Coverage reporting
pytest-asyncio>=0.21Async test support
mypy>=1.5Type checking
ruff>=0.1Linting and formatting

Development

Setting Up Development Environment

# Clone the repository
git clone https://github.com/elmiomar/mudparser.git
cd mudparser
# Create virtual environment
python -m venv venv
source venv/bin/activate # Linux/macOS# or
.\venv\Scripts\activate # Windows# Install in development mode with all extras
pip install -e ".[all]"

Running Tests

# Run all tests
pytest
# Run with verbose output
pytest -v
# Run with coverage report
pytest --cov=mudparser --cov-report=html
# Run specific test file
pytest tests/test_parser.py
# Run specific test
pytest tests/test_parser.py::TestParserFromFile::test_parse_amazon_echo_short
# Run tests matching a pattern
pytest -k "validation"

Code Quality

# Linting with ruff
ruff check src/
# Auto-fix linting issues
ruff check src/ --fix
# Format code
ruff format src/
# Type checking
mypy src/mudparser/

Building Documentation

# Install docs dependencies
pip install -e ".[docs]"# Serve documentation locally (with hot reload)
mkdocs serve
# Build static site
mkdocs build
# Deploy to GitHub Pages
mkdocs gh-deploy

Creating a Release

# Update version in pyproject.toml# Update CHANGELOG.md# Build package
python -m build
# Upload to PyPI
python -m twine upload dist/*

Troubleshooting

Common Issues

"ModuleNotFoundError: No module named 'mudparser'"

Solution: Install the package:

pip install mudparser
# or for development
pip install -e .

"MUDFileNotFoundError: File not found"

Solution: Check the file path:

frompathlibimportPath# Use absolute pathparser=MUDParser.from_file(Path("data/device.mud.json").absolute())
# Or verify file existspath=Path("device.mud.json")
ifnotpath.exists():
print(f"File not found: {path}")

"MUDSchemaError: Missing required field"

Solution: Ensure your MUD file has all required fields:

{
"ietf-mud:mud": {
"mud-version": 1,
"mud-url": "https://example.com/device.mud.json",
"last-update": "2024-01-15T00:00:00Z",
"cache-validity": 48,
"is-supported": true,
"from-device-policy": { ... },
"to-device-policy": { ... }
},
"ietf-access-control-list:access-lists": { ... }
}

Validation warnings about HTTP URLs

Solution: MUD URLs should use HTTPS:

{
"ietf-mud:mud": {
"mud-url": "https://example.com/device.mud.json"
}
}

Export missing DNS resolution

Note: MudParser exports DNS names as-is. For production use, resolve DNS names before applying rules:

importsocketdns_names=parser.get_dns_names()
fornameindns_names:
try:
ip=socket.gethostbyname(name)
print(f"{name} -> {ip}")
exceptsocket.gaierror:
print(f"Could not resolve: {name}")

Getting Help

  1. Check the documentation
  2. Search existing issues
  3. Open a new issue

Contributing

Contributions are welcome! Please follow these steps:

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Make your changes
  4. Run tests (pytest)
  5. Run linting (ruff check src/)
  6. Commit your changes (git commit -m 'Add amazing feature')
  7. Push to the branch (git push origin feature/amazing-feature)
  8. Open a Pull Request

Contribution Guidelines

  • Follow the existing code style
  • Add tests for new features
  • Update documentation as needed
  • Keep commits focused and atomic
  • Write clear commit messages

License

This project is licensed under the MIT License - see the LICENSE file for details.

MIT License
Copyright (c) 2024 Omar Ilias EL MIMOUNI
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

Acknowledgements

Standards and Specifications

Resources and Tools

Authors


Links


Built with Python and Pydantic | RFC 8520 & RFC 9761 Compliant

About

A tool for parsing MUD profiles.

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages