Compare commits

...

2 Commits

7 changed files with 209 additions and 0 deletions

1
.gitignore vendored
View File

@@ -2,3 +2,4 @@
**/.env **/.env
**/__pycache__ **/__pycache__
**/*.pem **/*.pem
**/.DS_Store

29
dns-config/README.md Normal file
View 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
View File

@@ -0,0 +1 @@
# Module

92
dns-config/__main__.py Normal file
View 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
View 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
View 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}")

4
requirements.txt Normal file
View File

@@ -0,0 +1,4 @@
requests
jmespath
azure-identity
azure-mgmt-dns