Skip to main content

WebDNS

The project aims to integrate a chain of automation processes to avoid manualy changes and erros during ip updates.

Service configuration

Two services are required for correct operation. The first is a dns server that will respond to requests from a specific network. The second lets you create and update network entry configuration files. Both are implemented through containers with Dockerfile and docker-compose.yml.

Configuration File

The webdns service has the configuration file 'webdns.conf'. In it we find the connection definitions with the netbox api, parameters, paths and ports:

[SETUP]
# Netbox URL
NB_URL = http://localhost

# Netbox token
#NB_TOKEN = token-hash

# HTTP Server PortNetbox token. Default is equal to 8181
#PORT = 8181

# Configuration files directory path
CF_PATH = /config/files/path

# Netbox prefix filter param vlan
#PARAMS_VLAN = vlan

Create Network Configuration Files

The 'create_cf()' function create DNS masq config files from Netbox prefixes filtered according to the 'role', 'site', 'status' and 'vlan' parameters. These files will be updated if there is any change in the ip addressing and if they receive the correct request

def create_cf():
    nb = api(url=nburl, token=nbtoken)

        # Get nb prefixes based on config params
        prefixes = nb.ipam.prefixes.filter(
            role=nbpfparams['role'],
            site=nbpfparams['site'],
            status=nbpfparams['status'],
            vlan=nbpfparams['vlan'],
        )

        # Iterate over child prefixes (cp), creating config files
        for cp in prefixes:
            # Get tenant and open config file
            tenant = cp.tenant.slug
            cfile = open(f'{cfpath}{tenant}.conf', 'w')

            # Get ip address from ip prefix and get CIDR
            net = ipaddress.IPv4Network(cp)

            ips_cidr = [f'{str(host)}/{str(net.prefixlen)}' for host in net.hosts()]
            for ip in ips_cidr:
                # Check whether ip addr has DNS name
                ip_obj = nb.ipam.ip_addresses.get(address=str(ip))
                if ip_obj is not None:
                    dns_name = ip_obj.dns_name

                # Pop subnet mask from ip addr and write on file
                str_ip, _ = ip.split('/')
                cfile.write(f'address=/{dns_name}/{str_ip}\n')

            # Save file and close
            cfile.close()
        return

Network File Update

In the api.py file we can find the Flask api initialization function and the network input files update function 'update_entries()', which is triggered to update the appropriate configuration file:

def update_entries():
    content_type = request.headers.get('Content-Type')
    if (content_type == 'application/json'):
        # Get data from request in Python format
        data = request.json

        # Validate data against JSON_UPDATE_SCHEMA
        data_is_valid = validate_json(data, JSON_UPDATE_SCHEMA)
        if not data_is_valid:
            return f'Invalid JSON data format. Please follow the following schema: {JSON_UPDATE_SCHEMA}\n'

        # Update config files based on input data
        update_cf(
            app.config['nburl'], 
            app.config['nbtoken'], 
            app.config['cfpath'], 
            data['tenant'], 
            data['ipaddresses']
        )
        return f'IP addresses updated on config file {data["tenant"]}.conf!\n'

    else:
        return 'Content-Type not supported!\n'

Arguments

Through the 'configparser' and 'os' libraries, file name changes can be performed by HTTP requests in POST. We find exemplified in the function get_configs():

def get_configs():
    config = configparser.ConfigParser()
    config.read_file(open(self.cpath, 'r'))
    self.nburl = config.get('SETUP', 'NB_URL')
    self.nbtoken = config.get('SETUP', 'NB_TOKEN')
    self.cfpath = config.get('SETUP', 'CF_PATH')
    self.port = config.get('SETUP', 'PORT')
    try: 
        self.nbpfparams['role'] = config.get('SETUP', 'PARAMS_ROLE')
    except configparser.NoOptionError:
        self.nbpfparams['role'] = None
    try:
        self.nbpfparams['status'] = config.get('SETUP', 'PARAMS_STATUS')
    except configparser.NoOptionError:
        self.nbpfparams['status'] = None
    try:
        self.nbpfparams['site'] = config.get('SETUP', 'PARAMS_SITE')
    except configparser.NoOptionError:
        self.nbpfparams['site'] = None
    try:
        self.nbpfparams['vlan'] = config.get('SETUP', 'PARAMS_VLAN')
    except configparser.NoOptionError:
        self.nbpfparams['vlan'] = None
    return

Making the Updates

The 'update_entries()' prepares the received JSON data and validates its format. After that, the 'update_cf()' function is called with the correct parameters to effectively update the network configuration file. The following is a example of an update request through an HTTP POST request with a valid JSON:

curl -H 'Content-Type: application/json' -X POST \
    -d '{"tenant": "ou-file-name",
         "ipaddresses":["access-ip",
                    "cpe-ip"]}' \
    localhost:8181/api/update_entries

The 'create_cf()' function can be found in methods.py file

def create_cf(cfpath, nburl, nbtoken, nbpfparams):
     # Check whether config file exists
    # TO-DO raise errors and deal with it
    filename = f'{cfpath}/{tenant}.conf'
    if not os.path.isfile(filename):
        print("Config file {} does not exist.".format(filename))
        exit(1)

    # Start nb connection
    nb = api(url=nburl, token=nbtoken)

    # Get nb ip addresses from Netbox
    ipaddrs = []
    for addr in ipaddresses:
        ipaddrs.append(
            nb.ipam.ip_addresses.get(
                address=addr,
            )
        )

    # Update IP addresses DNS masq entries based on dns_name from Netbox
    for ipaddr in ipaddrs:
        with open(filename, "r") as f:
            lines = f.readlines()
        new_lines = []
        for line in lines:
            if (ipaddr.dns_name in line) and (ipaddr.address not in line):
                new_lines.append(f'address=/{ipaddr.dns_name}/{ipaddr.address.split("/")[0]}\n')
            # If there is no change
            elif (ipaddr.dns_name not in line) and (ipaddr.address not in line):   
                new_lines.append(line)

        # Open configuration file for write
        with open(filename, "w") as f:
            f.writelines(new_lines)

    return

Metadata

Release files for webdns 0.0.11

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for webdns 0.0.11
File Size Uploaded Provenance Actions
webdns-0.0.11.tar.gz 9.4 kB Without provenance Details for webdns-0.0.11.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for webdns 0.0.11
File Interpreter ABI Platform Actions
webdns-0.0.11-py3-none-any.whl Python 3 none any Details for webdns-0.0.11-py3-none-any.whl

Total release size: 17.3 kB

Release files / webdns-0.0.11.tar.gz

Download URL webdns-0.0.11.tar.gz
Size 9.4 kB
Tags Source
SHA-256 checksum
How to use checksums
5231ca63a57eaf0b7aa806c73cfaec7d915c68ab5468780a53647e1fefe9719b
BLAKE2b-256 checksum
How to use checksums
1ebe2a56d25f4434e0e4b2af6031df45cb60cce8546c653b2b01f35646813d29
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/4.0.2 CPython/3.9.17

Release files / webdns-0.0.11-py3-none-any.whl

Download URL webdns-0.0.11-py3-none-any.whl
Size 7.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
db3b8265e69742ed31263e22612ef223cf6aa7a379c96871ae05a5dc0819b368
BLAKE2b-256 checksum
How to use checksums
b4647ab72197a58e00e0fa696d987b4c8e24f1d9c85167d28a714db00032570f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/4.0.2 CPython/3.9.17

Security for webdns 0.0.11

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page