This module implements the logic for applying a Domain Connect template to a zone.
Given a zone’s contents, a domain/host (subdomain), a template (identified by a providerId/serviceId), variable values, and additional parameters; the library will calculate changes to the zone.
Authorization of the user, verification that the user owns the domain, and the UX to gain consent from the user are left to the DNS Provider. But all the logic for handling the application of the template, including conflict detection, parameter processing, signature verification and more are handled in this library.
The library works by taking as canonical input the current content of the zone file corresponding to the domain name being processed. And it returns the modified zone file in the same canonical format. It is up to the implementation to translate to and from this canonical format.
Some DNS Providers only apply templates to a zone at run-time. After the zone is applied the records in the zone have no indication they were applied by a template. Other DNS Providers remember the records that were applied by a template, providing advanced features in their UX.
This library can deal with both types of DNS Providers. If a DNS Provider wishes to remember template state in its zone, it simply needs to read/write this additional state with the records.
The package can be installed from PyPi
pip install domainconnectzone
To install from teh source code repository execute:
python setup.py build
python setup.py install
This is a Python module and corresponding class that can handle applying a template to a zone of records.
The object is initialized with a providerId, serviceId, and templateDir.
from domainconnectzone import DomainConnect
dc = DomainConnect("<provider_id>", "<service_id>", "<template_dir>")
The provider id and service id map to the template name. Templates can be found
in the GitHub project at https://github.com/Domain-Connect/templates. Individual
template files are named <service_id>.<provider_id>.json
.
The location of the templates are a parameter to the object.
If the template cannot be found, an InvalidTemplate exception is thrown.
There are several other methods on this class/object.
The first (and more common) method is to apply changes to a zone based on the template.
Input takes a list of records that exist in the zone, the domain, the host, and additional parameters for the template as a dictionary of key/value pairs. It optionally takes a query string, sig, and key to verify the signature. And an optional flag indicating if the DNS Provider is multi-template-aware.
Parameter | Type | Description |
---|---|---|
Zone Records |
[{}] |
This is the list of records representing the zone. It is described below. |
Domain |
string |
This is the domain name. It matches the domain of the zone, and the domain the template will be applied to. |
Host |
string |
This is the host or subdomain. It can be None. |
Group Ids |
[string,] |
(optional) This is a list of groups to apply. If None or empty, the entire template will be applied. |
Query String (qs) |
string |
(optional) This is the query string used for signature verification. It should match the entire query string, properly URL Encoded, without the sig or key values. This defaults to None. |
Signature (sig) |
string |
(optional) This is the signature. This defaults to None. |
Key (key) |
string |
(optional) This is the key into DNS on syncPubKeyDomain to read the public key for signature verification. This defaults to None. |
*Ignore Signature (ignore_signature) * |
Boolean |
(optional) This tells the system to ignore any signature verification. Extreme care should be taken with this parameter, as it is largely intended for internal tooling and testing. This defaults to False. |
*Multi Template Aware (multi_aware) * |
Boolean |
(optional) This tells the system that the DNS Provider wishes to engage the multi-template processing. It defaults to False. |
import domainconnectzone
dc = domainconnectzone.DomainConnect("exampleservice.domainconnect.org", "template1")
zone_records = [...] # List of records in the zone
domain = 'example.com'
host = None
params = {'IP': '132.148.25.185', 'RANDOMTEXT': 'shm:1553436765:Hello'}
dc.apply_template(zone_records, domain, host, params)
Upon success this method returns a tuple containing three lists of zone records.
The first is the list new records being added.
Second is the list of records to be deleted.
The third is the list of final (complete) records that should be written to the zone.
Calling this function can throw any of a number of exceptions.
Exception | Description |
---|---|
HostRequired |
This is raised when the template requires a host, but no host (subdomain) is provided |
InvalidSignature |
This is raised when the template requires a signature and verification fails. |
MissingParameter |
This is raised when the template requires a parameter that wasn’t passed in. |
InvalidData |
This is raised when invalid data is passed into the template. Usually this is a parameter that results in malformed DNS data. |
This attribute returns True if the template requires signatures, False if not.
Records passed into and returned from the Apply method represent DNS records. These are implemented using a simple list of dictionary, with each dictionary representing a DNS record.
All records have a type (A, AAAA, CNAME, NS, TXT, MX, or SRV). Depending on the type there are other attributes.
If the DNS Provider wishes to implement template state in DNS, a set of fields is required in this data structure. This will be a dictionary. It is recommended that the DNS Provider store this by serializing the dictionary into a string.
Field | Type | Description |
---|---|---|
type |
string |
This is one of A, AAAA, CNAME, NS, TXT, MX, or SRV. |
name |
string |
This is the name/host of the record. This exists for all types. They must contain data that is relative to the root zone. For example, in the domain foo.com the name for the resolution of www.bar.foo.com would contain "www.bar". A value of @ or None would indicate the apex. |
data |
string |
This is the data for the record. This exists for all types. When the data contains a domain/host a fully qualified domain name without a trailing dot must be used. |
ttl |
int |
This is the TTL for the record. This exists for all types. |
priority |
int |
This is the priority of an MX record or SRV record. |
protocol |
string |
This is the protocol for an SRV record. This must be the value TCP or UDP. |
service |
string |
This is the service of an SRV record. |
weight |
int |
This is the weight of the SRV record. |
port |
int |
This is the port of the SRV record. |
_dc |
json |
(optional) This is the json structure representing the template state for applied records. The DNS Provider should store this by serializing/deserialzing the json, allowing for future extensibility. Fields in here are interesting to the DNS Provider and are documented below. |
_dc.id |
String |
This is the unique id representing the application of a template |
_dc.providerId |
String |
This is the providerId of the applied template on this record |
_dc.serviceId |
String |
This is the serviceId of the applied template on this record |
_dc.host |
String |
This was the host used to apply this template. All templates are scoped to the domain/host. |
_dc.essential |
String |
Largely internal, this indicates that the essential property on the record when applied. This is used for conflict detection when overwriting. |
An example zone:
[
{"type": "A","name": "@","data": "127.0.0.1","ttl": 3000},
{"type": "CNAME","name": "www","data": "@","ttl": 3000}
]
In addition to being used by the apply_template method, this independent method can be used to validate a query string against a signature and key.
import domainconnectzone
dc = domainconnectzone.DomainConnect('exampleservice.domainconnect.org', 'template2')
sig = 'LyCE+7H0zr/XHaxX36pdD1eSQENRiGTFxm79m7A5NLDPiUKLe71IrsEgnDLN76ndQcLTZlr4+HhpWzKZKyFl9ieEpNzZlDHRp35H83Erhm0eDctUmI1Zct51alZ8RuTL+aa29WC+AM7+gSpnL/AHl9mxckyeEuFFqXcl/3ShwK2F9x/7r+cICefiUEzsZN3EuqOvwqQkBSqcdVy/ohjNAG/InYAYSX+0fUK9UNQfQYkuPqOAptPRjX+hUnYsXUk/eQq16aX7TzhZm+eEq+En+oiEgh7qps1yvGbJm6QXKovan/sqng40R6FBP3R3dvfZC6QrfCUtGpQ8c0D0S5oLBw=='
key = '_dck1'
qs = 'domain=arnoldblinn.com&RANDOMTEXT=shm%3A1551036164%3Ahello&IP=132.148.25.185&host=bar'
dc.verify_sig(qs, sig, key)
If the signature fails, an InvalidSignature exception is raised
This method is useful for testing. It will prompt the user for all values for all variables in the template. These are added as key/values in a dictionary suitable for passing into the Apply function.
import domainconnectzone
dc = domainconnectzone.DomainConnect("example.com", "foo")
params = dc.prompt()
The DomainConnectTemplates
class is used to manage and validate DNS templates for Domain Connect. It handles loading, validating, creating, updating, and retrieving templates, ensuring that the templates conform to a specific schema and rules.
You can initialize the DomainConnectTemplates
class with or without a custom template directory path.
from domainconnectzone import DomainConnectTemplates
# Initialize with the default template path
templates = DomainConnectTemplates()
# Initialize with a custom template path
templates = DomainConnectTemplates('/path/to/custom/templates')
If no path is provided, the class uses the default template directory located in the same directory as the class file.
The schema
property returns the JSON schema used for validating templates, or None
if no schema is found.
from domainconnectzone import DomainConnectTemplates
templates = DomainConnectTemplates()
schema = templates.schema
The templates
property returns a list of available templates in the template directory. Each template is represented as a dictionary containing providerId
, serviceId
, fileName
, and the template’s JSON content.
from domainconnectzone import DomainConnectTemplates
templates = DomainConnectTemplates()
available_templates = templates.templates
Validates a given template against the schema and various rules, including checking for valid domain names and ensuring that certain fields do not contain variables.
from domainconnectzone import DomainConnectTemplates
template = {
"providerId": "exampleservice.domainconnect.org",
"providerName": "Example Domain Connect Service",
"serviceId": "test",
"serviceName": "Test",
"version": 1,
"records": [
{
"type": "TXT",
"host": "@",
"data": "%test%",
"ttl": 1800
}
]
}
templates = DomainConnectTemplates()
templates.validate_template(template)
If the template is invalid, it raises an InvalidTemplate
or InvalidData
exception.
Updates an existing template in the template directory after validating it. The template is identified by its providerId
and serviceId
.
from domainconnectzone import DomainConnectTemplates
template = {
...
}
templates = DomainConnectTemplates()
templates.update_template(template)
Raises an InvalidTemplate
exception if the template does not exist, or an EnvironmentError
if the template directory is not writable.
Creates a new template in the template directory after validating it. If a template with the same providerId
and serviceId
already exists, it raises an InvalidTemplate
exception.
from domainconnectzone import DomainConnectTemplates
template = {
...
}
templates = DomainConnectTemplates()
templates.create_template(template)
Raises an EnvironmentError
if the template directory is not writable.
Returns a dictionary of variable names and empty or previous values found in the template’s records.
You can optionally pass a variables
dictionary to provide previous values of the variables or a group
name to filter variables by group.
from domainconnectzone import DomainConnectTemplates
template = {
...
}
templates = DomainConnectTemplates()
variable_names = templates.get_variable_names(template)
The following exceptions can be raised by the DomainConnectTemplates
class:
-
InvalidTemplate
- Raised when a template is invalid or when certain operations fail. -
InvalidData
- Raised when invalid data (e.g., domain names) are detected within a template. -
EnvironmentError
- Raised when the template directory is not writable.
# Example: Loading templates and creating a new template
from domainconnectzone import DomainConnectTemplates
# Initialize the class
templates = DomainConnectTemplates('./test/templates')
# List available templates
for template_info in templates.templates:
print(template_info)
# Create a new template
new_template = {
"providerId": "exampleservice.domainconnect.org",
"providerName": "Example Domain Connect Service",
"serviceId": "test",
"serviceName": "Test",
"version": 1,
"records": [
{
"type": "TXT",
"host": "@",
"data": "%test%",
"ttl": 1800
}
]
}
templates.create_template(new_template)
variable_names = templates.get_variable_names(new_template)
print(variable_names)
Several helper functions are included for dealing with query strings.
This will convert a query string (qs) of the form a=1&b=2&c=3&d=4 to a dictionary of the form
{'a': '1', 'b': '2', 'c': '3', 'd': '4'}
.
This is useful for converting a query string to a dictionary, filtering out the values not useful as parameters (e.g. domain, host, sig, key).
import domainconnectzone
qs = 'a=1&b=2&c=3&d=4'
params = domainconnectzone.qsutil.qs2dict(qs, ['c', 'd'])
# params contains {'a': '1', 'b': '2'}
Execute ready shell script
./run_unittests_with_coverage.sh
This module is GoDaddy specific. This will prompt the user for domain/host/providerId/serviceId and GoDaddy API Key. It will read the template, prompt for all variable values, and apply the changes to the zone. This is done by using the API Key to read the entire zone, and write the entire zone.
import GDTest
GDTest.run()