Add dns-config module for Mail-in-a-Box DNS management and Azure integration
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -2,3 +2,4 @@
|
||||
**/.env
|
||||
**/__pycache__
|
||||
**/*.pem
|
||||
**/.DS_Store
|
||||
|
||||
29
dns-config/README.md
Normal file
29
dns-config/README.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# Mail-in-a-Box DNS Configuration Module
|
||||
|
||||
The `dns-config` module provides a command-line interface and a module to configure Mail-in-a-Box's server to delegate a DNS zone to Azure DNS. It retrieves the nameservers for a given Azure DNS zone and sets up the necessary DNS records in Mail-in-a-Box to delegate the zone to Azure DNS.
|
||||
|
||||
The module also contains additional commands to list Azure or Mail-in-a-Box DNS records. It can also be used to set, update, add or delete DNS records in Mail-in-a-Box.
|
||||
|
||||
## Usage
|
||||
|
||||
The following commands are available in the `dns-config` module:
|
||||
|
||||
- `list-azure`: List Azure DNS records for a given resource group and zone name.
|
||||
- `list-miab`: List Mail-in-a-Box DNS records of a given type (A, CNAME, MX, etc.).
|
||||
- `set`: Set a DNS record in Mail-in-a-Box.
|
||||
- `add`: Add a DNS record in Mail-in-a-Box.
|
||||
- `delete`: Delete a DNS record in Mail-in-a-Box.
|
||||
|
||||
Each command has its own `--help` option that provides more information on how to use it.
|
||||
|
||||
To automatically configure an Azure DNS zone in Mail-in-a-Box, you can use the following command:
|
||||
|
||||
```bash
|
||||
python -m dns-config configure-miab --resource-group <RESOURCE_GROUP> --zone-name <ZONE_NAME>
|
||||
```
|
||||
|
||||
Then list the Mail-in-a-Box DNS records to verify that the delegation records have been added:
|
||||
|
||||
```bash
|
||||
python -m dns-config list-miab --type NS
|
||||
```
|
||||
1
dns-config/__init__.py
Normal file
1
dns-config/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
# Module
|
||||
92
dns-config/__main__.py
Normal file
92
dns-config/__main__.py
Normal file
@@ -0,0 +1,92 @@
|
||||
import argparse
|
||||
from .azure import get_dns_nameservers
|
||||
from . import miab
|
||||
|
||||
def azure_list_command(args):
|
||||
nameservers = get_dns_nameservers(args.resource_group, args.zone_name)
|
||||
print(f"Nameservers for zone {args.zone_name}:")
|
||||
for ns in nameservers:
|
||||
print(f" - {ns}")
|
||||
|
||||
# Mail-in-a-Box API wrapper functions
|
||||
def miab_set_command(args):
|
||||
return miab.set_custom_dns(args.domain_name, args.record_type, args.record_value)
|
||||
|
||||
def miab_delete_command(args):
|
||||
return miab.delete_custom_dns(args.domain_name, args.record_type)
|
||||
|
||||
def miab_add_command(args):
|
||||
return miab.add_custom_dns(args.domain_name, args.record_type, args.record_value)
|
||||
|
||||
def miab_list_command(args):
|
||||
records = miab.list_custom_dns(args.record_type)
|
||||
if args.record_type:
|
||||
print(f"Custom {args.record_type} records:")
|
||||
else:
|
||||
print("Custom DNS records:")
|
||||
|
||||
for record in records:
|
||||
print(f" - {record['name']} ({record['type']}): {record['value']}")
|
||||
|
||||
def miab_configure_command(args):
|
||||
# Get a list of nameservers for the specified Azure DNS zone
|
||||
nameservers = get_dns_nameservers(args.resource_group, args.zone_name)
|
||||
|
||||
if not nameservers:
|
||||
print("No nameservers found. Aborting configuration.")
|
||||
return
|
||||
|
||||
# Add each nameserver as an NS record in the Mail-in-a-Box DNS server
|
||||
print(f"Configuring Mail-in-a-Box with nameservers from Azure DNS zone {args.zone_name}...")
|
||||
for ns in nameservers:
|
||||
print(f"Adding NS record for {ns}...")
|
||||
success = miab.add_custom_dns(args.zone_name, "NS", ns)
|
||||
if success:
|
||||
print(f"Successfully added NS record for {ns}")
|
||||
else:
|
||||
print(f"Failed to add NS record for {ns}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="DNS Configuration Tool")
|
||||
subparsers = parser.add_subparsers()
|
||||
|
||||
# list command - lists nameservers for a given Azure DNS zone
|
||||
list_azure_parser = subparsers.add_parser("list-azure", help="List nameservers for an Azure DNS zone")
|
||||
list_azure_parser.add_argument("--resource-group", required=True, help="Resource group name")
|
||||
list_azure_parser.add_argument("--zone-name", required=True, help="DNS zone name")
|
||||
list_azure_parser.set_defaults(func=azure_list_command)
|
||||
|
||||
# list-miab command - lists custom DNS records in the Mail-in-a-Box DNS server
|
||||
list_miab_parser = subparsers.add_parser("list-miab", help="List custom DNS records in the Mail-in-a-Box DNS server")
|
||||
list_miab_parser.add_argument("--record-type", help="Filter by DNS record type (e.g., A, CNAME, MX)")
|
||||
list_miab_parser.set_defaults(func=miab_list_command)
|
||||
|
||||
# set command - sets a record in the Mail-in-a-Box DNS server
|
||||
set_parser = subparsers.add_parser("set", help="Set a DNS record in the Mail-in-a-Box DNS server")
|
||||
set_parser.add_argument("--domain-name", required=True, help="Domain name to set the record for")
|
||||
set_parser.add_argument("--record-type", required=True, help="DNS record type (e.g., A, CNAME, MX)")
|
||||
set_parser.add_argument("--record-value", required=True, help="Value for the DNS record")
|
||||
set_parser.set_defaults(func=miab_set_command)
|
||||
|
||||
# delete command - deletes a record from the Mail-in-a-Box DNS server
|
||||
delete_parser = subparsers.add_parser("delete", help="Delete a DNS record from the Mail-in-a-Box DNS server")
|
||||
delete_parser.add_argument("--domain-name", required=True, help="Domain name to delete the record for")
|
||||
delete_parser.add_argument("--record-type", required=True, help="DNS record type (e.g., A, CNAME, MX)")
|
||||
delete_parser.add_argument("--record-value", help="Value for the DNS record")
|
||||
delete_parser.set_defaults(func=miab_delete_command)
|
||||
|
||||
# add command - adds a record to the Mail-in-a-Box DNS server
|
||||
add_parser = subparsers.add_parser("add", help="Add a DNS record to the Mail-in-a-Box DNS server")
|
||||
add_parser.add_argument("--domain-name", required=True, help="Domain name to add the record for")
|
||||
add_parser.add_argument("--record-type", required=True, help="DNS record type (e.g., A, CNAME, MX)")
|
||||
add_parser.add_argument("--record-value", required=True, help="Value for the DNS record")
|
||||
add_parser.set_defaults(func=miab_add_command)
|
||||
|
||||
# configure-miab - retrieves nameservers for an Azure DNS zone and configures them in the Mail-in-a-Box DNS server
|
||||
configure_miab_parser = subparsers.add_parser("configure-miab", help="Configure Mail-in-a-Box DNS server with nameservers from an Azure DNS zone")
|
||||
configure_miab_parser.add_argument("--resource-group", required=True, help="Azure Resource group name containing the DNS zone")
|
||||
configure_miab_parser.add_argument("--zone-name", required=True, help="DNS zone name")
|
||||
configure_miab_parser.set_defaults(func=miab_configure_command)
|
||||
|
||||
args = parser.parse_args()
|
||||
args.func(args)
|
||||
15
dns-config/azure.py
Normal file
15
dns-config/azure.py
Normal file
@@ -0,0 +1,15 @@
|
||||
from azure.identity import DefaultAzureCredential
|
||||
from azure.mgmt.dns import DnsManagementClient
|
||||
import os
|
||||
|
||||
AZURE_SUBSCRIPTION_ID = os.environ.get("AZURE_SUBSCRIPTION_ID", None)
|
||||
|
||||
def get_dns_nameservers(resource_group_name: str, zone_name: str, azure_subscription_id: str | None = None) -> list[str]:
|
||||
try:
|
||||
creds = DefaultAzureCredential()
|
||||
dns_client = DnsManagementClient(creds, azure_subscription_id or AZURE_SUBSCRIPTION_ID)
|
||||
zone = dns_client.zones.get(resource_group_name, zone_name)
|
||||
return zone.name_servers
|
||||
except Exception as e:
|
||||
print(f"Error fetching DNS nameservers: {e}")
|
||||
return []
|
||||
67
dns-config/miab.py
Normal file
67
dns-config/miab.py
Normal file
@@ -0,0 +1,67 @@
|
||||
from os import getenv
|
||||
from base64 import b64encode
|
||||
from urllib import parse
|
||||
from jmespath import search
|
||||
import requests
|
||||
|
||||
MIAB_USERNAME = getenv('MIAB_USERNAME', None) or getenv('MAILINABOX_EMAIL', None)
|
||||
MIAB_PASSWORD = getenv('MIAB_PASSWORD', None) or getenv('MAILINABOX_PASSWORD', None)
|
||||
MIAB_HOST = getenv('MIAB_HOST', None)
|
||||
|
||||
try:
|
||||
MIAB_HOST = parse.urlparse(getenv('MAILINABOX_BASE_URL', None)).hostname
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Prepare the Basic Auth header for MIAB API requests
|
||||
MIAB_AUTH_HEADER = "Basic " + b64encode(f"{MIAB_USERNAME}:{MIAB_PASSWORD}".encode()).decode()
|
||||
|
||||
def get_miab_url(domain_name: str, record_type: str = "A") -> str:
|
||||
if record_type == "A":
|
||||
return f"https://{MIAB_HOST}/admin/dns/custom/{domain_name}"
|
||||
else:
|
||||
return f"https://{MIAB_HOST}/admin/dns/custom/{domain_name}/{record_type.upper()}"
|
||||
|
||||
def set_custom_dns(domain_name: str, record_type: str = "A", record_value: str | None = None) -> bool:
|
||||
if not MIAB_HOST or not MIAB_USERNAME or not MIAB_PASSWORD:
|
||||
raise Exception("MIAB configuration is incomplete. Please set MIAB_HOST, MIAB_USERNAME, and MIAB_PASSWORD.")
|
||||
|
||||
url = get_miab_url(domain_name, record_type)
|
||||
payload = record_value if record_value else ""
|
||||
response = requests.put(url, data=payload, headers={"Authorization": MIAB_AUTH_HEADER, "Content-Type": "text/plain"})
|
||||
return response.status_code == 200
|
||||
|
||||
def delete_custom_dns(domain_name: str, record_type: str = "A", record_value: str | None = None) -> bool:
|
||||
if not MIAB_HOST or not MIAB_USERNAME or not MIAB_PASSWORD:
|
||||
raise Exception("MIAB configuration is incomplete. Please set MIAB_HOST, MIAB_USERNAME, and MIAB_PASSWORD.")
|
||||
|
||||
url = get_miab_url(domain_name, record_type)
|
||||
payload = record_value if record_value else ""
|
||||
response = requests.delete(url, data=payload, headers={"Authorization": MIAB_AUTH_HEADER, "Content-Type": "text/plain"})
|
||||
return response.status_code == 200
|
||||
|
||||
def add_custom_dns(domain_name: str, record_type: str = "A", record_value: str | None = None) -> bool:
|
||||
if not MIAB_HOST or not MIAB_USERNAME or not MIAB_PASSWORD:
|
||||
raise Exception("MIAB configuration is incomplete. Please set MIAB_HOST, MIAB_USERNAME, and MIAB_PASSWORD.")
|
||||
|
||||
url = get_miab_url(domain_name, record_type)
|
||||
payload = record_value if record_value else ""
|
||||
response = requests.post(url, data=payload, headers={"Authorization": MIAB_AUTH_HEADER, "Content-Type": "text/plain"})
|
||||
return response.status_code == 200
|
||||
|
||||
def list_custom_dns(record_type: str = None):
|
||||
response = requests.get(
|
||||
f"https://{MIAB_HOST}/admin/dns/custom",
|
||||
headers={"Authorization": MIAB_AUTH_HEADER}
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
records = response.json()
|
||||
if record_type:
|
||||
jmespath_expr = f"[?rtype=='{record_type.upper()}']"
|
||||
else:
|
||||
jmespath_expr = "[]"
|
||||
|
||||
return search(jmespath_expr + ".{name: qname, type: rtype, value: value}", records)
|
||||
else:
|
||||
raise Exception(f"Failed to retrieve DNS records: {response.status_code} {response.text}")
|
||||
Reference in New Issue
Block a user