hearth/scripts/validate-phase3.sh
Eric Garcia e78000831e Initial commit: Port infrastructure from coherence-mcp
Hearth is the infrastructure home for the letemcook ecosystem.

Ported from coherence-mcp/infra:
- Terraform modules (VPC, EKS, IAM, NLB, S3, storage)
- Kubernetes manifests (Forgejo, ingress, cert-manager, karpenter)
- Deployment scripts (phased rollout)

Status: Not deployed. EKS cluster needs to be provisioned.

Next steps:
1. Bootstrap terraform backend
2. Deploy phase 1 (foundation)
3. Deploy phase 2 (core services including Forgejo)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-24 06:06:13 -05:00

297 lines
8 KiB
Bash
Executable file

#!/usr/bin/env bash
#
# Phase 3 Validation: DNS and Email
#
# Validates that RFC 0041 components are deployed and healthy:
# - PowerDNS (responding, DNSSEC enabled)
# - Stalwart (SMTP, IMAP, TLS)
# - DNS records (SPF, DKIM, DMARC)
#
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
PASSED=0
FAILED=0
WARNINGS=0
# Configuration
DOMAIN="${DOMAIN:-coherence.dev}"
log_info() {
echo -e "${BLUE}[INFO]${NC} $1"
}
log_pass() {
echo -e "${GREEN}[PASS]${NC} $1"
((PASSED++))
}
log_fail() {
echo -e "${RED}[FAIL]${NC} $1"
((FAILED++))
}
log_warn() {
echo -e "${YELLOW}[WARN]${NC} $1"
((WARNINGS++))
}
check_powerdns() {
log_info "Checking PowerDNS status..."
# Check if namespace exists
if ! kubectl get namespace dns &> /dev/null; then
log_fail "dns namespace does not exist"
return
fi
# Check pod health
local ready_pods
ready_pods=$(kubectl -n dns get pods -l app=powerdns -o json | jq '[.items[] | select(.status.phase == "Running") | select(.status.containerStatuses[]?.ready == true)] | length')
if [ "$ready_pods" -ge 1 ]; then
log_pass "PowerDNS has $ready_pods ready pods"
else
log_fail "PowerDNS has no ready pods"
return
fi
# Check PowerDNS service
local svc_ip
svc_ip=$(kubectl -n dns get svc powerdns -o jsonpath='{.spec.clusterIP}' 2>/dev/null || echo "")
if [ -n "$svc_ip" ]; then
log_pass "PowerDNS service exists (ClusterIP: $svc_ip)"
else
log_fail "PowerDNS service does not exist"
fi
# Check external NLB
local nlb_hostname
nlb_hostname=$(kubectl -n dns get svc powerdns-external -o jsonpath='{.status.loadBalancer.ingress[0].hostname}' 2>/dev/null || echo "")
if [ -n "$nlb_hostname" ]; then
log_pass "PowerDNS NLB provisioned: $nlb_hostname"
else
log_warn "PowerDNS NLB not yet provisioned"
fi
}
check_dns_resolution() {
log_info "Checking DNS resolution..."
# Get NLB hostname
local nlb_hostname
nlb_hostname=$(kubectl -n dns get svc powerdns-external -o jsonpath='{.status.loadBalancer.ingress[0].hostname}' 2>/dev/null || echo "")
if [ -z "$nlb_hostname" ]; then
log_warn "Cannot test DNS resolution - NLB not provisioned"
return
fi
# Get NLB IP
local nlb_ip
nlb_ip=$(dig +short "$nlb_hostname" | head -1 || echo "")
if [ -z "$nlb_ip" ]; then
log_warn "Cannot resolve NLB hostname to IP"
return
fi
# Test DNS resolution against PowerDNS
local dns_response
dns_response=$(dig @"$nlb_ip" "$DOMAIN" +short 2>/dev/null || echo "")
if [ -n "$dns_response" ]; then
log_pass "DNS resolution working: $DOMAIN -> $dns_response"
else
log_warn "DNS resolution failed for $DOMAIN (may need NS delegation)"
fi
}
check_dnssec() {
log_info "Checking DNSSEC..."
# Check DNSSEC via public DNS
local dnssec_response
dnssec_response=$(dig +dnssec "$DOMAIN" 2>/dev/null | grep -c "ad" || echo "0")
if [ "$dnssec_response" -gt 0 ]; then
log_pass "DNSSEC is enabled and validated"
else
log_warn "DNSSEC not validated (may need DS record at registrar)"
fi
# Check for DNSKEY records
local dnskey_count
dnskey_count=$(dig DNSKEY "$DOMAIN" +short 2>/dev/null | wc -l | tr -d ' ')
if [ "$dnskey_count" -gt 0 ]; then
log_pass "DNSKEY records present ($dnskey_count keys)"
else
log_warn "No DNSKEY records found"
fi
}
check_stalwart() {
log_info "Checking Stalwart Mail Server..."
# Check if namespace exists
if ! kubectl get namespace email &> /dev/null; then
log_fail "email namespace does not exist"
return
fi
# Check pod health
local ready_pods
ready_pods=$(kubectl -n email get pods -l app=stalwart -o json | jq '[.items[] | select(.status.phase == "Running") | select(.status.containerStatuses[]?.ready == true)] | length')
if [ "$ready_pods" -ge 1 ]; then
log_pass "Stalwart has $ready_pods ready pods"
else
log_fail "Stalwart has no ready pods"
return
fi
# Check Stalwart services
local services=("smtp" "submission" "imap")
for svc in "${services[@]}"; do
local svc_exists
svc_exists=$(kubectl -n email get svc stalwart-"$svc" -o name 2>/dev/null || echo "")
if [ -n "$svc_exists" ]; then
log_pass "Stalwart $svc service exists"
else
log_warn "Stalwart $svc service not found"
fi
done
}
check_email_tls() {
log_info "Checking email TLS..."
# Get mail server hostname
local mail_host="mail.$DOMAIN"
# Check SMTP TLS (port 465)
if command -v openssl &> /dev/null; then
local smtp_tls
smtp_tls=$(echo | timeout 5 openssl s_client -connect "$mail_host":465 2>/dev/null | grep -c "Verify return code: 0" || echo "0")
if [ "$smtp_tls" -gt 0 ]; then
log_pass "SMTP TLS working on port 465"
else
log_warn "SMTP TLS check failed (may need DNS propagation)"
fi
# Check IMAP TLS (port 993)
local imap_tls
imap_tls=$(echo | timeout 5 openssl s_client -connect "$mail_host":993 2>/dev/null | grep -c "Verify return code: 0" || echo "0")
if [ "$imap_tls" -gt 0 ]; then
log_pass "IMAP TLS working on port 993"
else
log_warn "IMAP TLS check failed (may need DNS propagation)"
fi
else
log_warn "openssl not available - skipping TLS checks"
fi
}
check_email_dns_records() {
log_info "Checking email DNS records..."
# Check MX record
local mx_record
mx_record=$(dig MX "$DOMAIN" +short 2>/dev/null | head -1 || echo "")
if [ -n "$mx_record" ]; then
log_pass "MX record exists: $mx_record"
else
log_warn "No MX record found for $DOMAIN"
fi
# Check SPF record
local spf_record
spf_record=$(dig TXT "$DOMAIN" +short 2>/dev/null | grep "v=spf1" || echo "")
if [ -n "$spf_record" ]; then
log_pass "SPF record exists"
else
log_warn "No SPF record found"
fi
# Check DMARC record
local dmarc_record
dmarc_record=$(dig TXT "_dmarc.$DOMAIN" +short 2>/dev/null | grep "v=DMARC1" || echo "")
if [ -n "$dmarc_record" ]; then
log_pass "DMARC record exists"
else
log_warn "No DMARC record found"
fi
# Check DKIM record
local dkim_record
dkim_record=$(dig TXT "default._domainkey.$DOMAIN" +short 2>/dev/null | grep "v=DKIM1" || echo "")
if [ -n "$dkim_record" ]; then
log_pass "DKIM record exists"
else
log_warn "No DKIM record found (check selector name)"
fi
}
print_summary() {
echo ""
echo "========================================"
echo "Phase 3 Validation Summary"
echo "========================================"
echo -e " ${GREEN}Passed:${NC} $PASSED"
echo -e " ${RED}Failed:${NC} $FAILED"
echo -e " ${YELLOW}Warnings:${NC} $WARNINGS"
echo "========================================"
if [ "$FAILED" -gt 0 ]; then
echo ""
echo -e "${RED}Phase 3 validation FAILED${NC}"
echo "Please fix the issues above before proceeding to Phase 4."
exit 1
elif [ "$WARNINGS" -gt 0 ]; then
echo ""
echo -e "${YELLOW}Phase 3 validation passed with warnings${NC}"
echo "DNS propagation may take up to 48 hours."
echo "Review warnings above. You may proceed to Phase 4."
exit 0
else
echo ""
echo -e "${GREEN}Phase 3 validation PASSED${NC}"
echo "You may proceed to Phase 4."
exit 0
fi
}
main() {
echo "========================================"
echo "Phase 3 Validation: DNS and Email"
echo "========================================"
echo ""
check_powerdns
check_dns_resolution
check_dnssec
check_stalwart
check_email_tls
check_email_dns_records
print_summary
}
main "$@"