Skip to content

Commit

Permalink
Adds examples
Browse files Browse the repository at this point in the history
  • Loading branch information
joniumGit committed May 9, 2023
1 parent 17cf31c commit 1634aca
Show file tree
Hide file tree
Showing 7 changed files with 224 additions and 0 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ The DNSMule tool is composed in the following way:
- Rule
- Plugins

## Examples

Check out the examples in the [examples](examples) folder. They should get you up and running quickly.

#### Backends

Any backend can be used and registered in YAML, the only requirement is extending the `dnsmule.backends.Backend` class.
Expand Down
21 changes: 21 additions & 0 deletions examples/defining_configuration_in_yaml.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
"""
This example uses a YAML file to load rules and other configuration
The YAML file contains declarations to load the backend and rules for the mule.
The sample collects all A records and tags them with the first number of the IP address in the record.
The backend is the builtin DNSPythonBackend.
The storage is not defined in the YAML file, so it will default to the builtin DictStorage.
"""

from pathlib import Path

from dnsmule import DNSMule

mule = DNSMule.load(Path(__file__).parent / 'example.yml')

if __name__ == '__main__':
mule.scan('example.com')
print('Loaded Rules: ', *mule.rules)
print('Loaded Backend: ', mule.backend)
print('Loaded Storage: ', mule.storage)
print('Produced Result:', mule.result('example.com'))
41 changes: 41 additions & 0 deletions examples/defining_custom_file_backend.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
"""
This example uses a custom backend for DNSMule to read records from a file
The backend only needs to implement a single method that produces records.
The scan is then run with an arbitrary string to produce all records from the file.
A single rule is implemented to print out all CNAME records in the file.
"""
from pathlib import Path
from typing import Iterable

from dnsmule import DNSMule, Record, Domain, RRType
from dnsmule.backends.abstract import Backend


class FileBackend(Backend):
"""Implements a Backend for DNSMule that reads and splits lines in a file into a Record
"""
file: str

def _query(self, _: Domain, *types: RRType) -> Iterable[Record]:
_types = {*types}
with open(self.file, 'r') as f:
for line in f:
_type, _domain, _value = line.split(',')
_type = RRType.from_any(_type)
if _type in _types:
yield Record(domain=Domain(_domain), type=_type, data=_value)


mule = DNSMule.make(backend=FileBackend(file=Path(__file__).parent / 'example.domains'))


@mule.rules.add.CNAME
def aliases(record: Record):
"""Prints out all CNAME records found from the example file
"""
print('FOUND A CNAME: ', record.domain, '>>', record.text)


if __name__ == '__main__':
mule.scan('all domains in file')
67 changes: 67 additions & 0 deletions examples/defining_custom_ruletypes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
"""
This example shows how to register new rule creators in code
These rule creators can then be used when loading yaml files or creating rules in code.
"""
from dnsmule import Rules, Rule, Record, DNSMule
from dnsmule.loader import ConfigLoader

rules = Rules()
mule = DNSMule.make(rules=rules)


@rules.register('custom.factory')
def create_rule(**definition) -> Rule:
"""
Defines a handler function for creating rules
This can be used for creating any kind of rules and could be used for example defining presets for rules.
"""
print('Registering Rule from Function with definition:', definition)
return Rule(f=lambda r: r.tag('FACTORY'))


@rules.register
class CustomRule(Rule):
"""
Rules can also be registered as types inheriting from Rule
These can be useful as you can directly register classes with the _id attribute set to whatever
will be used in the YAML file. This is a good way to register rules if you prefer working with classes.
"""
_id = 'custom.rule'

def __init__(self, **kwargs):
super(CustomRule, self).__init__(**kwargs)
print('Registering Rule from Class with definition: ', kwargs)

def __call__(self, record: Record):
record.tag('RULE')


if __name__ == '__main__':
# Directly setting up and loading as opposed to loading the configuration from file
# using Mule.append(file)
loader = ConfigLoader(mule)
loader.config = {
'rules': [
{
'name': 'FACTORY',
'type': 'custom.factory',
'record': 'A',
'config': {
'custom': 'value',
}
},
{
'name': 'DIRECT',
'type': 'custom.rule',
'record': 'A',
'config': {
'hello': 'world',
}
},
]
}
loader.append_rules()
print('Loaded Rules:', *mule.rules)
74 changes: 74 additions & 0 deletions examples/defining_rules_in_code.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
"""
This example defines rules in code to implement a simple scanner for TXT and CNAME records
The rules are added directly by registering handlers in code.
It is important to remember to register the Backend implementation as it will default to the NOOPBackend.
The NOOPBackend is usually only useful when testing custom rules or loading rules.
"""

from dnsmule import DNSMule, Record
from dnsmule.backends.dnspython import DNSPythonBackend
from dnsmule.storages.abstract import result_to_json_data
from dnsmule.storages.dictstorage import DictStorage
from dnsmule.utils import extend_set

mule = DNSMule.make(
storage=DictStorage(),
backend=DNSPythonBackend(),
)


@mule.rules.add.CNAME
def alias_catcher(record: Record):
"""
Adds any CNAME records for a domain into the result
This can be useful in further analysis as the DNSPythonBackend uses the domain from recursive queries on
for example A records, which can lead to seemingly "missing" data as it gets attributed to a different domain
if a CNAME exists for the queried domain.
"""
extend_set(record.result.data, 'aliases', [record.text])


@mule.rules.add.TXT
def text_analyzer(record: Record):
"""Scans TXT records and matches a predefined list of interesting values
"""
text_record = record.text
if any(interesting_value in text_record for interesting_value in [
'verification',
'verifier',
'verify',
'domain',
'code',
'secret',
'key',
]):
extend_set(record.result.data, 'records', [text_record])
record.tag('INTERESTING')


def analyze(*domains):
"""Analyzes a set of domains and returns the results in a json compatible dict
"""
mule.scan(*domains)
return {
k: result_to_json_data(v)
for k, v in mule.storage.items()
}


if __name__ == '__main__':
import json
import argparse

parser = argparse.ArgumentParser(description='searches for interesting TXT records and any CNAME records')
parser.add_argument('domains', nargs='+', type=str, help='list of domains to scan')
arguments = parser.parse_args()

print('Rules: ', *mule.rules)
print('Results:', json.dumps(
analyze(*arguments.domains),
ensure_ascii=False,
indent=4,
))
2 changes: 2 additions & 0 deletions examples/example.domains
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
TXT,example.com,site-verification=abcd
CNAME,www.example.com,example.com
15 changes: 15 additions & 0 deletions examples/example.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
version: '0.5.0'
backend:
name: dnspython.DNSPythonBackend
config:
timeout: 2
description: This Storage has been configured in the YAML file
rules:
- name: DIGITS
type: dns.regex
record: A
description: Matches first Digit of the IP address
config:
pattern: '^(\d)'
group: 1
description: This Rule has been configured in the YAML file

0 comments on commit 1634aca

Please sign in to comment.