2
0
mirror of https://github.com/VinylDNS/vinyldns synced 2025-08-25 11:37:44 +00:00
vinyldns/utils/generate_zones_bind_api.py

246 lines
8.3 KiB
Python
Raw Normal View History

import os
import sys
import logging
from datetime import datetime
import subprocess
from typing import Optional, Tuple, List
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, EmailStr
import uvicorn
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.StreamHandler(sys.stdout)
]
)
logger = logging.getLogger(__name__)
class VinylDNSBindDNSManager:
def __init__(self,
zones_dir: str = "/etc/bind/vinyldns_zones",
vinyldns_zone_config: str = "/etc/bind/named.conf.vinyldns-zones",
zone_config: str = "/etc/bind/named.conf"):
self.zones_dir = zones_dir
self.vinyldns_zone_config = vinyldns_zone_config
self.zone_config = zone_config
try:
os.makedirs(zones_dir, exist_ok=True)
os.chmod(zones_dir, 0o755)
except Exception as e:
logger.error(f"Failed to VinylDNS create zones directory: {e}")
raise
def create_zone_file(self, zoneName: str, nameservers: List[str],
admin_email: str, ttl: int = 3600, refresh: int = 604800,
retry: int = 86400, expire: int = 2419200,
negative_cache_ttl: int = 604800) -> str:
"""
Create a VinylDNS zone file for BIND DNS server with multiple nameservers
"""
try:
admin_email = admin_email.replace('@', '.')
serial = datetime.now().strftime("%Y%m%d01")
zone_content = f""
# Add NS records for each nameserver
for ns in nameservers:
zone_content = f"""$TTL {ttl}
{zoneName} IN SOA {ns} {admin_email}. (
{serial} ; Serial
{refresh} ; Refresh
{retry} ; Retry
{expire} ; Expire
{negative_cache_ttl} ) ; Negative Cache TTL
{zoneName} IN NS {ns}
"""
zone_file_path = os.path.join(self.zones_dir, f"{zoneName}")
with open(zone_file_path, 'w') as f:
f.write(zone_content)
os.chmod(zone_file_path, 0o644)
logger.info(f"Created zone file for {zoneName} at {zone_file_path}")
return zone_file_path
except Exception as e:
logger.error(f"Failed to create zone file: {e}")
raise
def add_zone_config(self, zoneName: str, zone_file_path: str) -> None:
"""
Add VinylDNS zone configuration to BIND config file
"""
try:
config_content = f'''
zone "{zoneName}" {{
type master;
file "{zone_file_path}";
allow-update {{ any; }};
}};
'''
with open(self.vinyldns_zone_config, 'a') as f:
f.write(config_content)
named_config = 'include "/etc/bind/named.conf.vinyldns-zones";'
with open(self.zone_config, 'r+') as f:
content = f.read()
if named_config not in content:
f.write(f"\n{named_config}\n")
logger.info(f"Added VinylDNS zone configuration for {zoneName}")
except Exception as e:
logger.error(f"Failed to add VinylDNS zone configuration: {e}")
raise
def reload_bind(self, zoneName: str) -> Tuple[bool, Optional[str]]:
"""
Reload BIND configuration with error handling
"""
try:
# Step 1: Check if the BIND configuration file is valid
check_zone_config_result = subprocess.run(
['named-checkconf', "/etc/bind/named.conf"],
capture_output=True,
text=True,
timeout=10
)
if check_zone_config_result.returncode != 0:
logger.error(f"VinylDNS BIND config validation failed: {check_zone_config_result.stderr}")
return False, check_zone_config_result.stderr
else:
logger.info(f"VinylDNS BIND configuration validated successfully")
# Step 2: Check if the DNS zone file is valid using named-checkzone
check_zone_result = subprocess.run(
['named-checkzone', f'{zoneName}', f'{self.zones_dir}/{zoneName}'],
capture_output=True,
text=True,
timeout=10
)
if check_zone_result.returncode != 0:
logger.error(f"VinylDNS Zone file validation failed: {check_zone_result.stderr}")
return False, check_zone_result.stderr
else:
logger.info(f"VinylDNS Zone file '{zoneName}' validated successfully")
# Step 3: Stop the named service if config and zone checks pass
stop_result = subprocess.run(
['pkill', '-f', '/usr/sbin/named'],
capture_output=True,
text=True,
check=True
)
print("Stop command output:", stop_result.stdout)
# Step 4: Restart named service
restart_result = subprocess.run(
['/usr/sbin/named', '-c', '/etc/bind/named.conf'],
capture_output=True,
text=True,
check=True
)
print("Named command output:", restart_result.stdout)
logger.info("VinylDNS BIND service restarted successfully with the new zone file")
return True, None
except subprocess.TimeoutExpired:
logger.error("Configuration or VinylDNS zone file check timed out")
return False, "Configuration or VinylDNS zone file check timed out"
except subprocess.CalledProcessError as e:
logger.error(f"Error restarting the service: {e.stderr}")
return False, e.stderr
except Exception as e:
logger.error(f"Unexpected error: {e}")
return False, str(e)
# FastAPI Application Setup
app = FastAPI(
title="BIND DNS Management API",
description="API for creating VinylDNS BIND DNS zones and configurations",
version="1.0.0"
)
# Initialize DNS Manager
dns_manager = VinylDNSBindDNSManager()
class ZoneCreateRequest(BaseModel):
zoneName: str
nameservers: List[str]
admin_email: EmailStr
ttl: Optional[int] = 3600
refresh: Optional[int] = 604800
retry: Optional[int] = 86400
expire: Optional[int] = 2419200
negative_cache_ttl: Optional[int] = 604800
class APIResponse(BaseModel):
success: bool
message: str
data: Optional[dict] = None
# API Endpoints
@app.post("/api/zones/generate", response_model=APIResponse)
async def create_zone(zone_request: ZoneCreateRequest):
logger.info(f"Creating vinylDNS zone with request: {zone_request}")
try:
zone_file = dns_manager.create_zone_file(
zoneName=zone_request.zoneName,
nameservers=zone_request.nameservers,
admin_email=str(zone_request.admin_email),
ttl=zone_request.ttl,
refresh=zone_request.refresh,
retry=zone_request.retry,
expire=zone_request.expire,
negative_cache_ttl=zone_request.negative_cache_ttl
)
dns_manager.add_zone_config(zone_request.zoneName, zone_file)
success, error = dns_manager.reload_bind(zone_request.zoneName)
if not success:
raise HTTPException(
status_code=500,
detail=f"Failed to reload vinylDNS BIND: {error}"
)
return APIResponse(
success=True,
message=f"vinylDNS Zone {zone_request.zoneName} created successfully",
data={
"zoneName": zone_request.zoneName,
"zone_file": zone_file
}
)
except Exception as e:
logger.error(f"VinylDNS Zone creation failed: {e}")
raise HTTPException(
status_code=500,
detail=str(e)
)
@app.get("/api/health", response_model=APIResponse)
async def health_check():
return APIResponse(
success=True,
message="VinylDNS Zone creation BIND Service is running"
)
if __name__ == "__main__":
uvicorn.run(
"generate_zones_bind_api:app",
host="0.0.0.0",
port=19000,
reload=False
)