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>
297 lines
8 KiB
Bash
Executable file
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 "$@"
|