Building Secure Local SSL Infrastructure with DNS
Creating a secure internal SSL/TLS infrastructure is essential for protecting communication between services in local networks, development environments, and private clouds. This guide provides a complete implementation for building a production-ready local SSL infrastructure integrated with DNS for automatic certificate validation and distribution.
Architecture Overview
A properly designed local SSL infrastructure consists of several key components working together:
graph TB subgraph "Certificate Authority" CA[Root CA] ICA[Intermediate CA] CA --> ICA end
subgraph "DNS Infrastructure" DNS[Internal DNS Server] ACME[ACME DNS Challenge] DNS --> ACME end
subgraph "Certificate Management" CM[Cert Manager] VAULT[HashiCorp Vault] CM --> VAULT end
subgraph "Services" WEB[Web Services] API[API Services] DB[Database Services] end
ICA --> CM CM --> WEB CM --> API CM --> DB ACME --> CM
subgraph "Clients" BROWSER[Browsers] APP[Applications] CLI[CLI Tools] end
WEB --> BROWSER API --> APP DB --> CLISetting Up the Certificate Authority
Step 1: Create Root Certificate Authority
First, establish a secure root CA that will be the trust anchor for your infrastructure:
#!/bin/bash# ConfigurationCA_DIR="/etc/ssl/ca"ROOT_KEY="$CA_DIR/private/root-ca.key"ROOT_CERT="$CA_DIR/certs/root-ca.crt"ROOT_CONFIG="$CA_DIR/root-ca.conf"
# Create directory structuremkdir -p "$CA_DIR"/{certs,crl,newcerts,private,requests}chmod 700 "$CA_DIR/private"touch "$CA_DIR/index.txt"echo 1000 > "$CA_DIR/serial"
# Create Root CA configurationcat > "$ROOT_CONFIG" << 'EOF'[ ca ]default_ca = CA_default
[ CA_default ]dir = /etc/ssl/cacerts = $dir/certscrl_dir = $dir/crlnew_certs_dir = $dir/newcertsdatabase = $dir/index.txtserial = $dir/serialRANDFILE = $dir/private/.randprivate_key = $dir/private/root-ca.keycertificate = $dir/certs/root-ca.crtcrlnumber = $dir/crlnumbercrl = $dir/crl/root-ca.crlcrl_extensions = crl_extdefault_crl_days = 30default_md = sha256name_opt = ca_defaultcert_opt = ca_defaultdefault_days = 3650preserve = nopolicy = policy_loose
[ policy_loose ]countryName = optionalstateOrProvinceName = optionallocalityName = optionalorganizationName = optionalorganizationalUnitName = optionalcommonName = suppliedemailAddress = optional
[ req ]default_bits = 4096distinguished_name = req_distinguished_namestring_mask = utf8onlydefault_md = sha256x509_extensions = v3_ca
[ req_distinguished_name ]countryName = Country Name (2 letter code)stateOrProvinceName = State or Province NamelocalityName = Locality Name0.organizationName = Organization NameorganizationalUnitName = Organizational Unit NamecommonName = Common NameemailAddress = Email Address
[ v3_ca ]subjectKeyIdentifier = hashauthorityKeyIdentifier = keyid:always,issuerbasicConstraints = critical, CA:truekeyUsage = critical, digitalSignature, cRLSign, keyCertSign
[ v3_intermediate_ca ]subjectKeyIdentifier = hashauthorityKeyIdentifier = keyid:always,issuerbasicConstraints = critical, CA:true, pathlen:0keyUsage = critical, digitalSignature, cRLSign, keyCertSign
[ crl_ext ]authorityKeyIdentifier=keyid:alwaysEOF
# Generate Root CA private keyopenssl genrsa -aes256 -out "$ROOT_KEY" 4096chmod 400 "$ROOT_KEY"
# Generate Root CA certificateopenssl req -config "$ROOT_CONFIG" \ -key "$ROOT_KEY" \ -new -x509 -days 7300 -sha256 -extensions v3_ca \ -out "$ROOT_CERT" \ -subj "/C=US/ST=State/L=City/O=Organization/OU=IT/CN=Root CA"
# Verify Root CA certificateopenssl x509 -noout -text -in "$ROOT_CERT"Step 2: Create Intermediate Certificate Authority
The intermediate CA will issue certificates for services:
#!/bin/bash# ConfigurationCA_DIR="/etc/ssl/ca"INT_DIR="$CA_DIR/intermediate"INT_KEY="$INT_DIR/private/intermediate-ca.key"INT_CSR="$INT_DIR/csr/intermediate-ca.csr"INT_CERT="$INT_DIR/certs/intermediate-ca.crt"INT_CONFIG="$INT_DIR/intermediate-ca.conf"
# Create intermediate directory structuremkdir -p "$INT_DIR"/{certs,crl,csr,newcerts,private}chmod 700 "$INT_DIR/private"touch "$INT_DIR/index.txt"echo 1000 > "$INT_DIR/serial"echo 1000 > "$INT_DIR/crlnumber"
# Create Intermediate CA configurationcat > "$INT_CONFIG" << 'EOF'[ ca ]default_ca = CA_default
[ CA_default ]dir = /etc/ssl/ca/intermediatecerts = $dir/certscrl_dir = $dir/crlnew_certs_dir = $dir/newcertsdatabase = $dir/index.txtserial = $dir/serialRANDFILE = $dir/private/.randprivate_key = $dir/private/intermediate-ca.keycertificate = $dir/certs/intermediate-ca.crtcrlnumber = $dir/crlnumbercrl = $dir/crl/intermediate-ca.crlcrl_extensions = crl_extdefault_crl_days = 30default_md = sha256name_opt = ca_defaultcert_opt = ca_defaultdefault_days = 375preserve = nopolicy = policy_loose
[ policy_loose ]countryName = optionalstateOrProvinceName = optionallocalityName = optionalorganizationName = optionalorganizationalUnitName = optionalcommonName = suppliedemailAddress = optional
[ req ]default_bits = 4096distinguished_name = req_distinguished_namestring_mask = utf8onlydefault_md = sha256
[ req_distinguished_name ]countryName = Country Name (2 letter code)stateOrProvinceName = State or Province NamelocalityName = Locality Name0.organizationName = Organization NameorganizationalUnitName = Organizational Unit NamecommonName = Common NameemailAddress = Email Address
[ v3_intermediate_ca ]subjectKeyIdentifier = hashauthorityKeyIdentifier = keyid:always,issuerbasicConstraints = critical, CA:true, pathlen:0keyUsage = critical, digitalSignature, cRLSign, keyCertSign
[ server_cert ]basicConstraints = CA:FALSEnsCertType = servernsComment = "OpenSSL Generated Server Certificate"subjectKeyIdentifier = hashauthorityKeyIdentifier = keyid,issuer:alwayskeyUsage = critical, digitalSignature, keyEnciphermentextendedKeyUsage = serverAuthsubjectAltName = @alt_names
[ crl_ext ]authorityKeyIdentifier=keyid:always
[ ocsp ]basicConstraints = CA:FALSEsubjectKeyIdentifier = hashauthorityKeyIdentifier = keyid,issuerkeyUsage = critical, digitalSignatureextendedKeyUsage = critical, OCSPSigningEOF
# Generate Intermediate CA private keyopenssl genrsa -aes256 -out "$INT_KEY" 4096chmod 400 "$INT_KEY"
# Generate Intermediate CA certificate requestopenssl req -config "$INT_CONFIG" -new -sha256 \ -key "$INT_KEY" \ -out "$INT_CSR" \ -subj "/C=US/ST=State/L=City/O=Organization/OU=IT/CN=Intermediate CA"
# Sign Intermediate CA certificate with Root CAopenssl ca -config "$CA_DIR/root-ca.conf" \ -extensions v3_intermediate_ca \ -days 3650 -notext -md sha256 \ -in "$INT_CSR" \ -out "$INT_CERT"
# Create certificate chaincat "$INT_CERT" "$CA_DIR/certs/root-ca.crt" > "$INT_DIR/certs/ca-chain.crt"
# Verify certificate chainopenssl verify -CAfile "$CA_DIR/certs/root-ca.crt" "$INT_CERT"DNS Integration for Certificate Validation
CoreDNS Configuration with ACME Support
Configure CoreDNS to handle DNS challenges for automatic certificate issuance:
apiVersion: v1kind: ConfigMapmetadata: name: coredns-configdata: Corefile: | .:53 { errors health { lameduck 5s } ready
# Internal zone file /etc/coredns/zones/internal.zone internal.company.com { reload 30s }
# ACME DNS challenge support file /etc/coredns/zones/acme.zone _acme-challenge.internal.company.com { reload 5s }
# Forwarding for external domains forward . 8.8.8.8 8.8.4.4 { max_concurrent 1000 }
cache 30 loop reload loadbalance
# Prometheus metrics prometheus :9153
# Logging log . { class error } }
internal.zone: | $ORIGIN internal.company.com. $TTL 3600 @ IN SOA ns1.internal.company.com. admin.internal.company.com. ( 2024010101 ; Serial 3600 ; Refresh 1800 ; Retry 604800 ; Expire 86400 ; Minimum TTL )
; Name servers @ IN NS ns1.internal.company.com. @ IN NS ns2.internal.company.com.
; A records ns1 IN A 10.0.1.10 ns2 IN A 10.0.1.11 ca IN A 10.0.1.20 vault IN A 10.0.1.21
; Service records web IN A 10.0.2.10 api IN A 10.0.2.20 db IN A 10.0.2.30
; CNAME records www IN CNAME web
acme.zone: | $ORIGIN _acme-challenge.internal.company.com. $TTL 60 @ IN SOA ns1.internal.company.com. admin.internal.company.com. ( 2024010101 ; Serial 60 ; Refresh 30 ; Retry 3600 ; Expire 60 ; Minimum TTL )DNS-01 Challenge Automation
Implement automated DNS-01 challenge handling:
#!/usr/bin/env python3import osimport timeimport dns.resolverimport dns.updateimport dns.tsigkeyringimport dns.queryfrom flask import Flask, request, jsonify
app = Flask(__name__)
# ConfigurationDNS_SERVER = os.environ.get('DNS_SERVER', '10.0.1.10')DNS_ZONE = os.environ.get('DNS_ZONE', 'internal.company.com')TSIG_KEY_NAME = os.environ.get('TSIG_KEY_NAME', 'acme-key')TSIG_KEY = os.environ.get('TSIG_KEY', 'base64-encoded-key')TSIG_ALGO = os.environ.get('TSIG_ALGO', 'hmac-sha256')
# Create TSIG keyringkeyring = dns.tsigkeyring.from_text({ TSIG_KEY_NAME: TSIG_KEY})
@app.route('/present', methods=['POST'])def present_challenge(): """Add DNS TXT record for ACME challenge""" data = request.json domain = data.get('domain') token = data.get('token')
if not domain or not token: return jsonify({'error': 'Missing domain or token'}), 400
# Create DNS update update = dns.update.Update(DNS_ZONE, keyring=keyring, keyalgorithm=TSIG_ALGO)
# Add TXT record txt_name = f'_acme-challenge.{domain}' update.add(txt_name, 60, 'TXT', f'"{token}"')
try: response = dns.query.tcp(update, DNS_SERVER)
# Wait for DNS propagation time.sleep(5)
# Verify record exists resolver = dns.resolver.Resolver() resolver.nameservers = [DNS_SERVER] answers = resolver.resolve(f'{txt_name}.{DNS_ZONE}', 'TXT')
for rdata in answers: if token in str(rdata): return jsonify({'status': 'success'}), 200
return jsonify({'error': 'Record not found after creation'}), 500
except Exception as e: return jsonify({'error': str(e)}), 500
@app.route('/cleanup', methods=['POST'])def cleanup_challenge(): """Remove DNS TXT record after challenge completion""" data = request.json domain = data.get('domain')
if not domain: return jsonify({'error': 'Missing domain'}), 400
# Create DNS update update = dns.update.Update(DNS_ZONE, keyring=keyring, keyalgorithm=TSIG_ALGO)
# Delete TXT record txt_name = f'_acme-challenge.{domain}' update.delete(txt_name, 'TXT')
try: response = dns.query.tcp(update, DNS_SERVER) return jsonify({'status': 'success'}), 200 except Exception as e: return jsonify({'error': str(e)}), 500
if __name__ == '__main__': app.run(host='0.0.0.0', port=8080)Automated Certificate Management
Step-CA for ACME Protocol
Deploy Step-CA as an ACME server integrated with your CA:
{ "root": "/etc/step-ca/certs/root-ca.crt", "federatedRoots": [], "crt": "/etc/step-ca/certs/intermediate-ca.crt", "key": "/etc/step-ca/secrets/intermediate-ca.key", "address": ":443", "insecureAddress": "", "dnsNames": ["ca.internal.company.com"], "logger": { "format": "json" }, "db": { "type": "badgerv2", "dataSource": "/etc/step-ca/db" }, "authority": { "provisioners": [ { "type": "ACME", "name": "acme", "forceCN": true, "claims": { "maxTLSCertDuration": "720h", "defaultTLSCertDuration": "168h", }, }, { "type": "JWK", "name": "admin", "key": { "use": "sig", "kty": "EC", "kid": "admin-key-id", "crv": "P-256", "alg": "ES256", "x": "base64-x-coordinate", "y": "base64-y-coordinate", }, "encryptedKey": "encrypted-private-key", "claims": { "maxTLSCertDuration": "8760h", "defaultTLSCertDuration": "720h", "enableSSHCA": true, }, }, ], }, "tls": { "cipherSuites": [ "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", ], "minVersion": 1.2, "maxVersion": 1.3, "renegotiation": false, },}Cert-Manager Integration
Deploy cert-manager for Kubernetes environments:
apiVersion: cert-manager.io/v1kind: ClusterIssuermetadata: name: internal-ca-issuerspec: acme: server: https://ca.internal.company.com/acme/acme/directory email: admin@company.com privateKeySecretRef: name: internal-ca-account-key solvers: - dns01: webhook: groupName: acme.company.com solverName: internal-dns config: endpoint: http://dns-challenge-handler:8080 zone: internal.company.com---apiVersion: v1kind: Secretmetadata: name: ca-root-cert namespace: cert-managertype: Opaquedata: ca.crt: # base64 encoded root CA certificateCertificate Lifecycle Automation
#!/bin/bash# ConfigurationCERT_DIR="/etc/ssl/services"CA_URL="https://ca.internal.company.com"RENEWAL_DAYS=30LOG_FILE="/var/log/cert-manager.log"
# Logging functionlog() { echo "[$(date +'%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"}
# Check certificate expirationcheck_cert_expiry() { local cert_file=$1 local days_until_expiry
if [[ -f "$cert_file" ]]; then days_until_expiry=$(openssl x509 -enddate -noout -in "$cert_file" | \ cut -d= -f2 | xargs -I {} date -d {} +%s | \ awk -v now=$(date +%s) '{print int(($1-now)/86400)}')
echo "$days_until_expiry" else echo "-1" fi}
# Request new certificate using ACMErequest_certificate() { local domain=$1 local cert_path="$CERT_DIR/$domain"
log "Requesting certificate for $domain"
# Create certificate directory mkdir -p "$cert_path"
# Request certificate using step CLI step ca certificate "$domain" \ "$cert_path/cert.pem" \ "$cert_path/key.pem" \ --ca-url="$CA_URL" \ --root="/etc/ssl/ca/certs/root-ca.crt" \ --acme="$CA_URL/acme/acme/directory" \ --kty=RSA \ --size=2048
if [[ $? -eq 0 ]]; then log "Certificate successfully obtained for $domain"
# Set proper permissions chmod 644 "$cert_path/cert.pem" chmod 600 "$cert_path/key.pem"
# Create combined certificate chain cat "$cert_path/cert.pem" \ "/etc/ssl/ca/intermediate/certs/intermediate-ca.crt" \ > "$cert_path/fullchain.pem"
return 0 else log "ERROR: Failed to obtain certificate for $domain" return 1 fi}
# Renew certificatesrenew_certificates() { local services_file="/etc/ssl/services.conf"
while IFS= read -r line; do # Skip comments and empty lines [[ "$line" =~ ^#.*$ ]] || [[ -z "$line" ]] && continue
# Parse service configuration domain=$(echo "$line" | cut -d':' -f1) service=$(echo "$line" | cut -d':' -f2) reload_cmd=$(echo "$line" | cut -d':' -f3)
cert_file="$CERT_DIR/$domain/cert.pem" days_left=$(check_cert_expiry "$cert_file")
if [[ $days_left -lt $RENEWAL_DAYS ]]; then log "Certificate for $domain expires in $days_left days, renewing..."
if request_certificate "$domain"; then # Reload service if specified if [[ -n "$reload_cmd" ]]; then log "Reloading service: $service" eval "$reload_cmd" fi fi else log "Certificate for $domain is valid for $days_left more days" fi
done < "$services_file"}
# Main executionmain() { log "Starting certificate lifecycle manager"
# Ensure required directories exist mkdir -p "$CERT_DIR"
# Run certificate renewal check renew_certificates
log "Certificate lifecycle check completed"}
# Run main functionmainService Configuration Examples
Nginx with Auto-Renewed Certificates
server { listen 443 ssl http2; listen [::]:443 ssl http2; server_name app.internal.company.com;
# Certificate paths ssl_certificate /etc/ssl/services/app.internal.company.com/fullchain.pem; ssl_certificate_key /etc/ssl/services/app.internal.company.com/key.pem;
# Modern SSL configuration ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256; ssl_prefer_server_ciphers off;
# OCSP stapling ssl_stapling on; ssl_stapling_verify on; ssl_trusted_certificate /etc/ssl/services/app.internal.company.com/fullchain.pem;
# Security headers add_header Strict-Transport-Security "max-age=63072000" always; add_header X-Frame-Options "SAMEORIGIN" always; add_header X-Content-Type-Options "nosniff" always; add_header X-XSS-Protection "1; mode=block" always;
# Application configuration location / { proxy_pass http://localhost:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; }}
# Redirect HTTP to HTTPSserver { listen 80; listen [::]:80; server_name app.internal.company.com; return 301 https://$server_name$request_uri;}PostgreSQL with SSL/TLS
# postgresql.confssl = onssl_cert_file = '/etc/ssl/services/db.internal.company.com/cert.pem'ssl_key_file = '/etc/ssl/services/db.internal.company.com/key.pem'ssl_ca_file = '/etc/ssl/ca/certs/ca-chain.crt'ssl_crl_file = ''ssl_ciphers = 'HIGH:MEDIUM:+3DES:!aNULL'ssl_prefer_server_ciphers = onssl_ecdh_curve = 'prime256v1'ssl_min_protocol_version = 'TLSv1.2'ssl_max_protocol_version = ''
# pg_hba.conf# TYPE DATABASE USER ADDRESS METHODhostssl all all 10.0.0.0/16 cert clientcert=verify-fullhostssl replication replicator 10.0.0.0/16 cert clientcert=verify-fullClient Certificate Management
Generating Client Certificates
#!/bin/bashCLIENT_NAME="$1"CLIENT_DIR="/etc/ssl/clients/$CLIENT_NAME"
if [[ -z "$CLIENT_NAME" ]]; then echo "Usage: $0 <client-name>" exit 1fi
# Create client directorymkdir -p "$CLIENT_DIR"
# Generate client private keyopenssl genrsa -out "$CLIENT_DIR/client.key" 2048chmod 600 "$CLIENT_DIR/client.key"
# Create client certificate requestcat > "$CLIENT_DIR/client.conf" << EOF[req]distinguished_name = req_distinguished_namereq_extensions = v3_reqprompt = no
[req_distinguished_name]C = USST = StateL = CityO = OrganizationOU = ITCN = $CLIENT_NAMEemailAddress = $CLIENT_NAME@internal.company.com
[v3_req]keyUsage = critical, digitalSignature, keyEnciphermentextendedKeyUsage = clientAuthsubjectAltName = @alt_names
[alt_names]email = $CLIENT_NAME@internal.company.comEOF
# Generate certificate requestopenssl req -new -key "$CLIENT_DIR/client.key" \ -out "$CLIENT_DIR/client.csr" \ -config "$CLIENT_DIR/client.conf"
# Sign client certificateopenssl ca -config /etc/ssl/ca/intermediate/intermediate-ca.conf \ -extensions usr_cert -days 365 -notext -md sha256 \ -in "$CLIENT_DIR/client.csr" \ -out "$CLIENT_DIR/client.crt"
# Create PKCS12 bundle for easy importopenssl pkcs12 -export \ -out "$CLIENT_DIR/client.p12" \ -inkey "$CLIENT_DIR/client.key" \ -in "$CLIENT_DIR/client.crt" \ -certfile /etc/ssl/ca/intermediate/certs/ca-chain.crt \ -passout pass:changeme
echo "Client certificate generated for $CLIENT_NAME"echo "Certificate: $CLIENT_DIR/client.crt"echo "Private Key: $CLIENT_DIR/client.key"echo "PKCS12 Bundle: $CLIENT_DIR/client.p12"Monitoring and Maintenance
Certificate Monitoring Dashboard
graph LR subgraph "Monitoring Stack" PROM[Prometheus] ALERT[Alertmanager] GRAF[Grafana] end
subgraph "Certificate Checks" EXP[Expiry Check] VAL[Validation Check] REV[Revocation Check] end
subgraph "Services" WEB[Web Services] API[API Services] DB[Databases] end
EXP --> PROM VAL --> PROM REV --> PROM
WEB --> EXP API --> EXP DB --> EXP
PROM --> ALERT PROM --> GRAFPrometheus Certificate Exporter
#!/usr/bin/env python3import sslimport socketimport datetimeimport timefrom prometheus_client import start_http_server, Gauge
# Metricscert_expiry_days = Gauge('cert_expiry_days', 'Days until certificate expiry', ['hostname', 'port'])cert_valid = Gauge('cert_valid', 'Certificate validation status', ['hostname', 'port'])
def check_certificate(hostname, port=443): """Check SSL certificate for a given host""" try: # Create SSL context context = ssl.create_default_context()
# Connect and get certificate with socket.create_connection((hostname, port), timeout=10) as sock: with context.wrap_socket(sock, server_hostname=hostname) as ssock: cert = ssock.getpeercert()
# Parse expiry date not_after = datetime.datetime.strptime( cert['notAfter'], '%b %d %H:%M:%S %Y %Z' )
# Calculate days until expiry days_left = (not_after - datetime.datetime.utcnow()).days
# Update metrics cert_expiry_days.labels(hostname=hostname, port=port).set(days_left) cert_valid.labels(hostname=hostname, port=port).set(1)
return days_left
except Exception as e: print(f"Error checking {hostname}:{port} - {str(e)}") cert_valid.labels(hostname=hostname, port=port).set(0) return -1
def main(): # Start Prometheus metrics server start_http_server(9100)
# Services to monitor services = [ ('web.internal.company.com', 443), ('api.internal.company.com', 443), ('db.internal.company.com', 5432), ]
while True: for hostname, port in services: check_certificate(hostname, port)
# Check every 5 minutes time.sleep(300)
if __name__ == '__main__': main()Security Best Practices
1. Key Management
# Secure key storage with proper permissionschmod 700 /etc/ssl/ca/privatechmod 600 /etc/ssl/ca/private/*.key
# Use hardware security modules (HSM) for production# Example with SoftHSMsofthsm2-util --init-token --slot 0 --label "CA Keys" \ --pin 1234 --so-pin 5678
# Store CA key in HSMpkcs11-tool --module /usr/lib/softhsm/libsofthsm2.so \ --login --pin 1234 \ --write-object /etc/ssl/ca/private/root-ca.key \ --type privkey --id 01 --label "Root CA Key"2. Certificate Pinning
// Node.js example with certificate pinningconst https = require("https");const crypto = require("crypto");
const pinnedCerts = ["sha256//BASE64_ENCODED_CERT_FINGERPRINT"];
const options = { hostname: "api.internal.company.com", port: 443, path: "/", method: "GET", checkServerIdentity: (host, cert) => { const fingerprint = crypto .createHash("sha256") .update(cert.raw) .digest("base64");
if (!pinnedCerts.includes(`sha256//${fingerprint}`)) { throw new Error("Certificate pin validation failed"); } },};
https .request(options, res => { // Handle response }) .end();3. Audit Logging
#!/bin/bash# Enable audit logging for CA operationscat >> /etc/ssl/ca/intermediate/intermediate-ca.conf << EOF
[ ca ]# Existing configuration...audit_log = /var/log/ca-audit.log
EOF
# Log rotation configurationcat > /etc/logrotate.d/ca-audit << EOF/var/log/ca-audit.log { daily rotate 365 compress delaycompress missingok notifempty create 0600 root root sharedscripts postrotate # Signal CA service to reopen logs if needed systemctl reload step-ca 2>/dev/null || true endscript}EOFTroubleshooting Common Issues
DNS Resolution Issues
# Test DNS resolutiondig _acme-challenge.app.internal.company.com TXT @10.0.1.10
# Verify TSIG keynsupdate -k /etc/bind/acme-key.key <<EOFserver 10.0.1.10zone internal.company.comupdate add test.internal.company.com 60 A 10.0.0.1sendEOF
# Check DNS logsjournalctl -u coredns -fCertificate Validation Problems
# Verify certificate chainopenssl verify -CAfile /etc/ssl/ca/certs/ca-chain.crt \ /etc/ssl/services/app.internal.company.com/cert.pem
# Check certificate detailsopenssl x509 -in /etc/ssl/services/app.internal.company.com/cert.pem \ -noout -text
# Test SSL connectionopenssl s_client -connect app.internal.company.com:443 \ -CAfile /etc/ssl/ca/certs/ca-chain.crt \ -servername app.internal.company.comConclusion
Building a secure local SSL infrastructure with DNS integration provides a robust foundation for internal service communication. By combining a proper PKI hierarchy with automated certificate management and DNS-based validation, organizations can achieve enterprise-grade security for their internal networks while maintaining ease of use and automation capabilities.
The key to success is proper planning, automation of routine tasks, and regular monitoring of certificate health. With the tools and configurations provided in this guide, you can implement a production-ready SSL infrastructure that scales with your organization’s needs.