feat(docker): 메일 엔진 전용 DNS 리졸버(unbound)와 Spamhaus 조회 키(DQS)

엔진의 DNS 조회(DNSBL · SPF · DKIM · DMARC · MX · PTR · TLSA)를 공용 · ISP DNS 가 아니라
이 서버의 리졸버로 푼다. Spamhaus 는 공용 DNS 를 거친 조회를 거부하고, DNSSEC 을 검증해야
엔진의 DANE 이 켜지기 때문이다.

- mail-resolver: unbound 1.26.2(digest 고정 · 비-root · cap_drop ALL). mail 과만 닿는
  internal 네트워크 mail-dns 와 바깥 길 mail-dns-egress. 사설 주소의 답 · 질의를 막는다.
- setup: 바꾸기 전에 리졸버 네트워크 · 주소 · DNSSEC 을 확인한다(UDP 는 루트 SOA, TCP 는
  루트 DNSKEY — UDP DNSKEY 는 루트 키 교체 기간에 잘려 오탐이 난다). 엔진 DNS 는 운영 Custom,
  개발 System. Spamhaus 키가 있으면 시험 항목을 확인한 뒤 DQS 로 켜고 없으면 두 항목을 끈다.
  키는 모든 출력에서 가린다.
- mail 은 리졸버를 기다리되(required: false) 리졸버 오류 하나로 메일 전체가 서지 않게 한다.
- README: 리졸버와 스팸 차단 목록, 운영 배포 차례, 리졸버 장애 때(리로드 거절 · restart 비상 길),
  DNSSEC 이 깨진 도메인의 예외.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-30 19:01:12 +09:00
parent 4047bd30e7
commit 9466a85daa
8 changed files with 1180 additions and 47 deletions
+11
View File
@@ -189,10 +189,21 @@ jobs:
echo "::error::경로가 붙은 NGINX_BACKEND_UPSTREAM 을 받아들였습니다 (거부해야 한다)"; exit 1
fi
# 메일 전용 DNS 리졸버(mail-resolver)의 설정 검사 — compose 와 같은 이미지 · 같은 사용자(101:102)로 unbound-checkconf.
# 설정 파일은 바인드 마운트 대신 빌드 컨텍스트로 넣는다 (위 머리말 — 잡 컨테이너의 경로는 호스트에 없다)
- name: unbound 설정 검사
run: |
touch backend/.env.production
image=$(docker compose -f docker-compose.yml --env-file .env.production.example config --images | grep unbound)
tag="ci-unbound-config:${{ github.run_id }}"
printf 'FROM %s\nCOPY unbound.conf /etc/unbound/unbound.conf\n' "$image" | docker build -t "$tag" -f- mail/resolver
docker run --rm --user 101:102 --entrypoint unbound-checkconf "$tag"
- name: 정리
if: always()
run: |
docker image rm -f "ci-nginx-config:${{ github.run_id }}" || true
docker image rm -f "ci-unbound-config:${{ github.run_id }}" || true
# 게시 주소 확인이 중간에 끊겼을 때 남은 컨테이너 (보통은 run --rm 이 지운다)
docker ps -aq --filter "label=com.docker.compose.project=ci-bind-${{ github.run_id }}" | xargs -r docker rm -f || true