보안
보안 (Security)
출처: Caddy 공식 문서
본문
Caddy는 HTTPS를 자동으로 그리고 기본적으로 사용한 최초의 웹 서버예요.
자동 HTTPS는 모든 사이트에 TLS 인증서를 프로비저닝하고 계속 갱신해줘요. 또한 HTTP를 HTTPS로 리다이렉트해줘요! Caddy는 안전하고 현대적인 기본값을 사용해요. 다운타임, 추가 구성, 별도 도구가 필요 없어요.
팁 Caddy가 자동 HTTPS 기술을 개척했어요. 우리는 2015년에 가능해진 첫날부터 이걸 해왔어요. Caddy의 HTTPS 자동화 로직은 세계에서 가장 성숙하고 견고해요.
28초짜리 동영상이 어떻게 작동하는지 보여줘요:
메뉴:
- 개요 (Overview)
- 활성화 (Activation)
- 효과 (Effects)
- 호스트 이름 요구사항 (Hostname requirements)
- 로컬 HTTPS (Local HTTPS)
- 테스트 (Testing)
- ACME 챌린지 (ACME Challenges)
- 온디맨드 TLS (On-Demand TLS)
- 오류 (Errors)
- 스토리지 (Storage)
- 와일드카드 인증서 (Wildcard certificates)
- 암호화된 ClientHello (ECH)
개요
기본적으로 Caddy는 모든 사이트를 HTTPS로 서빙해요.
- Caddy는 IP 주소와 로컬/내부 호스트 이름을, 로컬에서 자동으로 신뢰되는(허용되는 경우) 자체 서명 인증서로 HTTPS를 통해 서빙해요.
- 예:
localhost,127.0.0.1
- 예:
- Caddy는 Let's Encrypt나 ZeroSSL 같은 공개 ACME CA의 인증서로 HTTPS를 통해 공개 DNS 이름을 서빙해요.
- 예:
example.com,sub.example.com,*.example.com
- 예:
Caddy는 모든 관리 인증서를 계속 갱신하고 HTTP(기본 포트 80)를 HTTPS(기본 포트 443)로 자동 리다이렉트해요.
로컬 HTTPS:
- Caddy는 고유한 루트 인증서를 신뢰 저장소에 설치하기 위해 비밀번호를 요구할 수 있어요. 이는 루트당 한 번만 발생하며 언제든 제거할 수 있어요.
- Caddy의 루트 CA 인증서를 신뢰하지 않고 사이트에 접근하는 모든 클라이언트는 보안 오류를 보여줘요.
공개 도메인 이름:
팁 이는 Caddy뿐 아니라 모든 기본 프로덕션 웹사이트의 일반적인 요구사항이에요. 주요 차이는 Caddy를 실행하기 전에 DNS 레코드를 제대로 설정해 인증서를 프로비저닝할 수 있게 하는 것이에요.
- 도메인의 A/AAAA 레코드가 서버를 가리키고,
- 포트
80과443이 외부에서 열려 있고, - Caddy가 그 포트에 바인딩할 수 있고(또는 그 포트가 Caddy로 포워딩되고),
- 데이터 디렉터리가 쓰기 가능하고 영구적이며,
- 도메인 이름이 구성의 어딘가 관련 있는 곳에 나타나면,
사이트는 자동으로 HTTPS로 서빙돼요. 그것 외에는 아무것도 할 필요 없어요. 그냥 작동해요!
HTTPS는 공유된 공개 인프라를 활용하므로, 서버 관리자로서 이 페이지의 나머지 정보를 이해해 불필요한 문제를 피하고, 발생 시 해결하며, 고급 배포를 제대로 구성해야 해요.
활성화
Caddy는 서빙하는 도메인 이름(즉 호스트 이름)이나 IP 주소를 알 때 자동 HTTPS를 암시적으로 활성화해요. 실행/구성 방식에 따라 Caddy에 도메인/IP를 알리는 다양한 방법이 있어요:
다음 중 하나라도 자동 HTTPS가 전체 또는 부분적으로 활성화되는 것을 막아요:
- JSON을 통해 또는 Caddyfile을 통해 명시적으로 비활성화
- 구성에 호스트 이름이나 IP 주소를 제공하지 않음
- HTTP 포트에서만 수신 대기
- Caddyfile에서 사이트 주소에
http://접두사 사용 - 수동으로 인증서 로드(
ignore_loaded_certificates가 설정되지 않은 한)
특수한 경우:
.ts.net으로 끝나는 도메인은 Caddy가 관리하지 않아요. 대신 Caddy는 로컬에서 실행 중인 Tailscale 인스턴스로부터 핸드셰이크 시점에 이 인증서를 자동으로 얻으려 시도해요. 이를 위해 Tailscale 계정에서 HTTPS가 활성화되어 있어야 하고, Caddy 프로세스가 root로 실행되거나tailscaled를 구성해 Caddy 사용자에게 인증서 가져오기 권한을 줘야 해요.
효과
자동 HTTPS가 활성화되면 다음이 발생해요:
- 자격이 되는 모든 도메인 이름에 대한 인증서 획득 및 갱신
- HTTP가 HTTPS로 리다이렉트됨(HTTP 포트
80사용)
자동 HTTPS는 명시적 구성을 절대 덮어쓰지 않고 보강만 해요.
이미 HTTP 포트에서 수신 대기하는 서버가 있다면, HTTP→HTTPS 리다이렉트 라우트가 호스트 매처와 함께 여러분의 라우트 뒤에, 그러나 사용자 정의 catch-all 라우트 앞에 삽입돼요.
필요하면 자동 HTTPS를 커스터마이즈하거나 비활성화할 수 있어요. 예를 들어 특정 도메인 이름을 건너뛰거나 리다이렉트를 비활성화할 수 있어요(Caddyfile에서는 글로벌 옵션으로).
호스트 이름 요구사항
모든 호스트 이름(도메인 이름)은 다음 조건을 충족하면 완전 관리 인증서에 자격이 돼요:
- 비어 있지 않고
- 영숫자, 하이픈, 점, 와일드카드(
*)만으로 구성되며 - 점으로 시작하거나 끝나지 않아야 함(RFC 1034)
추가로 호스트 이름은 다음 조건을 충족하면 공개 신뢰 인증서에 자격이 돼요:
- localhost가 아니며(
.localhost,.local,.internal,.home.arpaTLD 포함) - IP 주소가 아니고
- 가장 왼쪽 라벨로 단일 와일드카드
*만 가짐
로컬 HTTPS
Caddy는 호스트(도메인, IP, 호스트 이름)가 지정된 모든 사이트에 HTTPS를 자동으로 사용해요. 내부 및 로컬 호스트도 포함이에요. 일부 호스트는 공개적이지 않거나(예: 127.0.0.1, localhost) 일반적으로 공개 신뢰 인증서에 자격이 없어요(예: IP 주소 - 인증서를 얻을 수는 있지만 일부 CA에서만 가능). 이들은 비활성화하지 않는 한 여전히 HTTPS로 서빙돼요.
비공개 사이트를 HTTPS로 서빙하기 위해 Caddy는 자체 인증 기관(CA)을 생성하고 그것으로 인증서를 서명해요. 신뢰 체인은 루트 및 중간 인증서로 구성돼요. 리프 인증서는 중간 인증서가 서명해요. 이들은 Caddy의 데이터 디렉터리의 pki/authorities/local에 저장돼요.
Caddy의 로컬 CA는 Smallstep 라이브러리로 구동돼요.
로컬 HTTPS는 ACME를 사용하지 않으며 DNS 검증을 수행하지 않아요. 로컬 머신에서만 작동하고 CA의 루트 인증서가 설치된 곳에서만 신뢰돼요.
CA 루트
루트의 개인 키는 암호학적으로 안전한 의사 난수 소스로 고유하게 생성되고 제한된 권한으로 스토리지에 저장돼요. 서명 작업을 수행할 때만 메모리에 로드된 후 범위를 벗어나 가비지 컬렉션되도록 해요.
Caddy는 (비준수 클라이언트를 지원하기 위해) 루트로 직접 서명하도록 구성될 수 있지만, 이는 기본적으로 비활성화되어 있고 루트 키는 중간 인증서 서명에만 사용돼요.
루트 키가 처음 사용될 때 Caddy는 시스템의 로컬 신뢰 저장소에 설치하려 시도해요. 권한이 없으면 비밀번호를 요구해요. 이 동작은 caddyfile에서 skip_install_trust나 json 구성에서 "install_trust": false로 비활성화할 수 있어요. 권한 없는 사용자로 실행되어 실패하면 권한 있는 사용자로 caddy trust를 실행해 설치를 재시도할 수 있어요.
팁 컴퓨터가 손상되지 않고 고유한 루트 키가 유출되지 않는 한, 자기 머신에 Caddy의 루트 인증서를 신뢰하는 것은 안전해요.
Caddy의 루트 CA가 설치된 후 로컬 신뢰 저장소에 "Caddy Local Authority"로 보여요(다른 이름을 구성하지 않은 한). 원하면 언제든 제거할 수 있어요(caddy untrust 명령이 쉽게 해줘요).
인증서를 로컬 신뢰 저장소에 자동으로 설치하는 것은 편의를 위한 것일 뿐 항상 작동한다는 보장은 없어요. 특히 컨테이너를 사용하거나 Caddy를 권한 없는 시스템 서비스로 실행하는 경우가 그래요. 궁극적으로 내부 PKI에 의존한다면 Caddy의 루트 CA가 필요한 신뢰 저장소에 제대로 추가되도록 하는 것은 시스템 관리자의 책임이에요(이것은 웹 서버 범위 밖이에요).
CA 중간 인증서
중간 인증서와 키도 생성되며, 이를 사용해 리프(개별 사이트) 인증서를 서명해요.
루트 인증서와 달리 중간 인증서는 수명이 훨씬 짧고 필요에 따라 자동으로 갱신돼요.
테스트
Caddy 구성을 테스트하거나 실험하려면 ACME 엔드포인트를 스테이징이나 개발 URL로 변경하는지 확인하세요. 그렇지 않으면 어떤 비율 제한에 걸렸는지에 따라 HTTPS 접근을 최대 일주일까지 차단할 수 있는 비율 제한에 부딪힐 가능성이 높아요.
Caddy의 기본 CA 중 하나는 Let's Encrypt이며, 이는 같은 비율 제한이 적용되지 않는 스테이징 엔드포인트를 갖고 있어요:
https://acme-staging-v02.api.letsencrypt.org/directory
ACME 챌린지
공개 신뢰 TLS 인증서를 얻으려면 공개 신뢰 3rd-party 기관의 검증이 필요해요. 요즘 이 검증 프로세스는 ACME 프로토콜로 자동화되며, 아래 설명된 세 가지 방법("챌린지 타입") 중 하나로 수행할 수 있어요.
처음 두 챌린지 타입은 기본적으로 활성화돼요. 여러 챌린지가 활성화되면 Caddy는 특정 챌린지에 대한 우연한 의존을 피하기 위해 하나를 무작위로 선택해요. 시간이 지나면서 어떤 챌린지 타입이 가장 성공적인지 학습해 그것을 먼저 선호하기 시작하지만, 필요하면 다른 사용 가능한 챌린지 타입으로 폴백해요.
HTTP 챌린지
HTTP 챌린지는 대상 호스트 이름의 A/AAAA 레코드에 대해 권위 있는 DNS 조회를 수행한 다음, 포트 80을 통해 HTTP로 임시 암호화 리소스를 요청해요. CA가 기대하는 리소스를 보면 인증서가 발급돼요.
이 챌린지는 포트 80이 외부에서 접근 가능해야 해요. Caddy가 포트 80에서 수신 대기할 수 없으면 포트 80의 패킷을 Caddy의 HTTP 포트로 포워딩해야 해요.
이 챌린지는 기본적으로 활성화되어 있고 명시적 구성이 필요 없어요.
TLS-ALPN 챌린지
TLS-ALPN 챌린지는 대상 호스트 이름의 A/AAAA 레코드에 대해 권위 있는 DNS 조회를 수행한 다음, 특별한 ServerName과 ALPN 값을 포함하는 TLS 핸드셰이크로 포트 443을 통해 임시 암호화 리소스를 요청해요. CA가 기대하는 리소스를 보면 인증서가 발급돼요.
이 챌린지는 포트 443이 외부에서 접근 가능해야 해요. Caddy가 포트 443에서 수신 대기할 수 없으면 포트 443의 패킷을 Caddy의 HTTPS 포트로 포워딩해야 해요.
이 챌린지는 기본적으로 활성화되어 있고 명시적 구성이 필요 없어요.
DNS 챌린지
DNS 챌린지는 대상 호스트 이름의 TXT 레코드에 대해 권위 있는 DNS 조회를 수행하고 특정 값을 가진 특별한 TXT 레코드를 찾아요. CA가 기대하는 값을 보면 인증서가 발급돼요.
이 챌린지는 열린 포트가 필요 없고 인증서를 요청하는 서버가 외부에서 접근 가능할 필요도 없어요. 하지만 DNS 챌린지는 구성이 필요해요. Caddy는 특별한 TXT 레코드를 설정(및 해제)할 수 있도록 도메인의 DNS 제공자에 접근할 자격 증명을 알아야 해요. DNS 챌린지가 활성화되면 다른 챌린지는 기본적으로 비활성화돼요.
ACME CA는 챌린지 검증을 위해 TXT 레코드를 조회할 때 DNS 표준을 따르므로, CNAME 레코드를 사용해 챌린지 응답을 다른 DNS 영역에 위임할 수 있어요. _acme-challenge 서브도메인을 다른 영역에 위임하는 데 사용할 수 있어요. 이는 DNS 제공자가 API를 제공하지 않거나 Caddy의 DNS 플러그인 중 하나가 지원하지 않을 때 특히 유용해요.
DNS 제공자 지원은 커뮤니티 노력이에요. 위키에서 여러분의 제공자에 대한 DNS 챌린지를 활성화하는 방법을 알아보세요.
온디맨드 TLS
Caddy는 **온디맨드 TLS(On-Demand TLS)**라는 새 기술을 개척했어요. 이는 구성 로드 시가 아니라 처음 TLS 핸드셰이크가 필요로 할 때 동적으로 새 인증서를 얻어요. 핵심적으로 이는 도메인 이름을 구성에 미리 하드코딩할 필요가 없다는 뜻이에요.
많은 기업이 수만 개의 사이트를 서빙할 때 더 낮은 비용과 운영 골칫거리 없이 TLS 배포를 확장하기 위해 이 고유한 기능에 의존해요.
온디맨드 TLS는 다음 경우에 유용해요:
- 서버를 시작하거나 리로드할 때 모든 도메인 이름을 알지 못할 때,
- 도메인 이름이 바로 제대로 구성되지 않을 수 있을 때(DNS 레코드가 아직 설정되지 않음),
- 도메인 이름을 통제하지 않을 때(예: 고객 도메인).
온디맨드 TLS가 활성화되면 인증서를 얻기 위해 구성에 도메인 이름을 지정할 필요가 없어요. 대신 Caddy가 아직 인증서가 없는 서버 이름(SNI)에 대한 TLS 핸드셰이크를 받으면, Caddy가 핸드셰이크를 완료하는 데 사용할 인증서를 얻는 동안 핸드셰이크가 보류돼요. 지연은 보통 몇 초에 불과하고 그 첫 핸드셰이크만 느려요. 이후 모든 핸드셰이크는 인증서가 캐시되고 재사용되며 갱신이 백그라운드에서 일어나므로 빠르고. 이후 핸드셰이크는 인증서를 갱신 상태로 유지하기 위한 유지보수를 트리거할 수 있지만, 이 유지보수는 인증서가 아직 만료되지 않았다면 백그라운드에서 발생해요.
온디맨드 TLS 사용
온디맨드 TLS는 악용을 방지하기 위해 반드시 활성화 그리고 제한되어야 해요.
온디맨드 TLS 활성화는 JSON 구성을 사용한다면 TLS 자동화 정책에서, Caddyfile을 사용한다면 사이트 블록의 tls 지시문에서 발생해요.
이 기능의 악용을 방지하려면 제한을 구성해야 해요. 이는 JSON 구성의 automation 객체나 Caddyfile의 on_demand_tls 전역 옵션에서 이루어져요. 제한은 "전역적"이며 사이트별이나 도메인별로 구성할 수 없어요. 주요 제한은 "ask" 엔드포인트로, Caddy가 핸드셰이크의 도메인에 대한 인증서를 얻고 관리할 권한이 있는지 물어보는 HTTP 요청을 보내요. 즉 데이터베이스의 계정 테이블을 조회해 고객이 그 도메인 이름으로 가입했는지 확인할 수 있는 몇몇 내부 백엔드가 필요할 거예요.
CA가 인증서를 발급하는 속도에 유의하세요. 몇 초 이상 걸리면 사용자 경험에 부정적인 영향을 미칠 수 있어요(첫 클라이언트에게만).
지연된 특성과 악용 방지에 필요한 추가 구성 때문에, 여러분의 실제 사용 사례가 위에서 설명된 경우일 때만 온디맨드 TLS를 활성화하는 것을 권장해요.
온디맨드 TLS를 효과적으로 사용하는 방법에 대한 자세한 내용은 위키 기사를 참고하세요.
오류
Caddy는 인증서 관리에 오류가 발생해도 계속하려 최선을 다해요.
기본적으로 인증서 관리는 백그라운드에서 수행돼요. 이는 시작을 차단하지 않고 사이트를 느리게 하지 않는다는 뜻이에요. 하지만 모든 인증서가 준비되기 전에도 서버가 실행된다는 뜻이기도 해요. 백그라운드로 실행하면 Caddy가 오랜 기간 동안 지수 백오프로 재시도할 수 있어요.
인증서 획득이나 갱신에 오류가 생기면 어떻게 되는지:
- Caddy가 잠시 멈췄다가 우연이었는지 확인하려고 한 번 재시도해요
- Caddy가 잠시 멈췄다가 다음 활성화된 챌린지 타입으로 전환해요
- 모든 활성화된 챌린지 타입을 시도한 후 다음 구성된 발급자(issuer)를 시도해요
- Let's Encrypt
- ZeroSSL
- 모든 발급자를 시도한 후 지수 백오프
- 시도 간 최대 1일
- 최대 30일 동안
Let's Encrypt로 재시도하는 동안 Caddy는 비율 제한 문제를 피하기 위해 그들의 스테이징 환경으로 전환해요. 완벽한 전략은 아니지만 일반적으로 도움이 돼요.
ACME 챌린지는 적어도 몇 초가 걸리고, 내부 비율 제한은 우발적 악용을 완화하는 데 도움을 줘요. Caddy는 여러분이나 CA가 구성한 것에 더해 내부 비율 제한을 사용해서, 백만 개의 도메인 이름을 담은 접시를 Caddy에 건네줘도 가능한 한 빠르지만 점진적으로 모두에 대한 인증서를 얻을 수 있게 해요. Caddy의 내부 비율 제한은 현재 ACME 계정당 10초당 10회 시도예요.
리소스 누출을 피하기 위해 Caddy는 구성이 변경되면 진행 중인 작업(ACME 트랜잭션 포함)을 중단해요. Caddy가 빈번한 구성 리로드를 처리할 수 있지만, 이런 운영 고려 사항에 유의하고 구성 변경을 배치해 리로드를 줄이고 Caddy가 백그라운드에서 인증서 획득을 실제로 마칠 기회를 주는 것을 고려하세요.
발급자 폴백
Caddy는 인증서를 성공적으로 얻지 못할 경우 다른 CA로 완전히 이중화된 자동 장애 조치(failover)를 지원하는 최초의(그리고 지금까지 유일한) 서버예요.
기본적으로 Caddy는 두 개의 ACME 호환 CA를 활성화해요: Let's Encrypt와 ZeroSSL. Caddy가 Let's Encrypt에서 인증서를 얻지 못하면 ZeroSSL로 시도해요. 둘 다 실패하면 백오프하고 나중에 다시 재시도해요. 구성에서 Caddy가 인증서를 얻는 데 사용할 발급자를 보편적으로 또는 특정 이름에 대해 커스터마이즈할 수 있어요.
스토리지
Caddy는 공개 인증서, 개인 키 및 기타 자산을 구성된 스토리지 시설(또는 구성되지 않았다면 기본 스토리지, 자세한 내용은 링크 참고)에 저장해요.
기본 구성을 사용할 때 알아야 할 핵심은 $HOME 폴더가 쓰기 가능하고 영구적이어야 한다는 것이에요. 문제 해결을 돕기 위해 Caddy는 --environ 플래그가 지정되면 시작 시 환경 변수를 출력해요.
같은 스토리지를 사용하도록 구성된 모든 Caddy 인스턴스는 해당 리소스를 자동으로 공유하고 클러스터로 인증서 관리를 조정해요.
ACME 트랜잭션을 시도하기 전에 Caddy는 구성된 스토리지를 테스트해 쓰기 가능하고 충분한 용량이 있는지 확인해요. 이는 불필요한 잠금 경합을 줄이는 데 도움이 돼요.
와일드카드 인증서
Caddy는 자격이 되는 와일드카드 이름의 사이트를 서빙하도록 구성되면 와일드카드 인증서를 얻고 관리할 수 있어요. 가장 왼쪽 도메인 라벨만 와일드카드라면 사이트 이름이 와일드카드에 자격이 돼요. 예를 들어 *.example.com은 자격이 되지만, sub.*.example.com, foo*.example.com, *bar.example.com, *.*.example.com은 자격이 없어요. (이것은 WebPKI의 제한이에요.)
Caddyfile을 사용하면 Caddy는 인증서 주체 이름에 관해 사이트 이름을 문자 그대로 받아들여요. 즉 sub.example.com으로 정의된 사이트는 Caddy가 sub.example.com에 대한 인증서를 관리하게 하고, *.example.com으로 정의된 사이트는 Caddy가 *.example.com에 대한 와일드카드 인증서를 관리하게 해요. 이는 일반적인 Caddyfile 패턴 페이지에서 확인할 수 있어요. 다른 동작이 필요하다면 JSON 구성이 인증서 주체와 사이트 이름("호스트 매처")을 더 정밀하게 제어할 수 있게 해줘요.
Caddy 2.10부터 와일드카드 인증서를 자동화할 때 Caddy는 구성의 개별 서브도메인에 와일드카드 인증서를 사용해요. 명시적으로 구성하지 않는 한(force_automate 등) 개별 서브도메인에 대한 인증서를 얻지 않아요.
와일드카드 인증서는 광범위한 권한을 나타내므로, 개별 인증서를 관리하면 PKI에 부담을 주거나 CA가 시행하는 비율 제한에 부딪힐 만큼 서브도메인이 많을 때, 또는 키가 손상될 경우 DNS 영역의 많은 부분을 노출시키는 위험을 감수할 만한 프라이버시 절충이 있을 때만 사용해야 해요. 와일드카드 인증서 자체는 특정 서브도메인을 숨기는 프라이버시를 제공하지 않는다는 점에 유의하세요. Encrypted ClientHello(ECH)가 활성화되지 않으면 여전히 TLS ClientHello 패킷에 노출돼요. (아래 참고.)
참고: Let's Encrypt는 와일드카드 인증서를 얻으려면 DNS 챌린지를 요구해요.
암호화된 ClientHello (ECH)
보통 TLS 핸드셰이크는 Server Name Indicator(SNI; 연결되는 도메인)를 포함한 ClientHello를 평문으로 보내는 것을 포함해요. 핸드셰이크 이후 연결을 암호화하는 데 필요한 파라미터를 포함하고 있기 때문이에요. 이는 당연히 ClientHello에서 가장 민감한 부분인 도메인 이름을, 즉시 물리적으로 가까이 있지 않아도 연결을 도청할 수 있는 누구에게나 노출해요. 대상 IP가 많은 다른 사이트를 서빙할 때 어떤 서비스에 연결하는지 드러내며, 일부 정부가 인터넷을 검열하는 방식이기도 해요.
Encrypted ClientHello를 사용하면 클라이언트가 "내부" ClientHello를 해독하기 위한 파라미터를 설정하는 "외부" ClientHello로 실제 ClientHello를 감싸 도메인 이름을 보호할 수 있어요. 하지만 이것이 작동하고 실제 프라이버시 이점을 제공하려면 많은 움직이는 부품이 완벽하게 맞아야 해요.
먼저 클라이언트는 ClientHello를 암호화하는 데 사용할 파라미터, 즉 구성을 알아야 해요. 이 정보는 공개 키와 "외부" 도메인("공개 이름") 등을 포함해요. 이 구성은 신뢰할 수 있는 방식으로 게시되거나 배포되어야 해요.
이론상 종이에 적어 모두에게 나눠줄 수도 있지만, 대부분의 주요 브라우저는 사이트에 연결할 때 ECH 파라미터를 포함하는 HTTPS 타입 DNS 레코드를 조회하는 것을 지원해요. 따라서 (1) ECH 구성(공개/개인 키 쌍 등)을 생성한 다음 (2) base64로 인코딩된 ECH 구성을 포함하는 HTTPS 타입 DNS 레코드를 만들어야 해요.
아니면... Caddy가 그 모든 것을 대신 해주게 할 수 있어요. Caddy는 ECH 구성을 자동으로 생성, 게시, 서빙할 수 있는 최초이자 유일한 웹 서버예요.
HTTPS 레코드가 게시되면 클라이언트는 사이트에 연결할 때 HTTPS 레코드에 대한 DNS 조회를 수행해야 해요. 보통 DNS 조회는 평문이라 결과 ECH 핸드셰이크의 보안을 손상시키므로, 브라우저는 DNS-over-HTTPS(DoH)나 DNS-over-TLS(DoT) 같은 보안 DNS 프로토콜을 사용해야 해요. 브라우저에 따라 수동으로 활성화해야 할 수 있어요.
클라이언트가 ECH 구성을 안전하게 다운로드한 후 내장된 공개 키로 ClientHello를 암호화하고 사이트에 계속 연결해요. 그러면 Caddy가 내부 ClientHello를 해독하고 도메인 이름이 와이어상 평문으로 나타나지 않고 사이트를 계속 서빙해요.
배포 고려 사항
ECH는 미묘한 기술이에요. Caddy가 ECH를 완전히 자동화해도 최대 프라이버시 이점을 위해 많은 것을 고려해야 해요. 또한 다양한 절충에 대해 알고 있어야 해요.
게시
Caddy는 도메인에 이미 레코드가 있는 경우에만 HTTPS 레코드를 만들어요. 이는 와일드카드가 커버하는 서브도메인의 DNS 조회를 깨지 않도록 방지해요. 사이트에 적어도 서버를 가리키는 A/AAAA 레코드가 있는지 확인하세요. DNS 레코드에 와일드카드만 사용한다면 와일드카드 도메인도 Caddy 구성에 나타나야 해요.
Caddy는 CNAME 레코드가 있는 도메인에 HTTPS 레코드를 게시하지 않아요.
ECH GREASE
Wireshark를 열고 최신 버전의 Firefox나 Chrome 같은 주요 브라우저(ECH가 비활성화된 경우에도)로 어떤 사이트에든 연결하면, 핸드셰이크에 encrypted_client_hello 확장이 포함된 것을 볼 수 있어요:

이것의 목적은 실제 ECH 핸드셰이크를 평문 핸드셰이크와 구별할 수 없게 만드는 것이에요. ECH 핸드셰이크가 일반 것과 다르게 보이면 검열자가 최소한의 피해로 ECH 핸드셰이크를 차단할 수 있어요. 하지만 그럴듯한 ECH 확장이 있는 모든 핸드셰이크를 차단하면 본질적으로 인터넷 대부분을 꺼버리게 돼요. (목표는 광범위한 검열의 비용을 높이는 것이에요.)
이것은 연결 문제를 해결할 때 주로 알아두면 중요해요.
키 회전
인증서 키처럼 같은 키를 오랫동안 사용하는 것은 좋은 관행이 아니며(심지어 불안전할 수 있음), 그래서 ECH 키는 정기적으로 회전되어야 해요. 인증서와 달리 ECH 구성은 엄격히 만료되지 않아요. 하지만 서버는 그래도 회전해야 해요.
키 회전은 까다로운데, 클라이언트가 업데이트된 키를 알아야 하기 때문이에요. 서버가 단순히 이전 키를 새 키로 교체하면 클라이언트가 새 키를 즉시 알지 못하는 한 모든 ECH 핸드셰이크가 실패해요. 하지만 단순히 업데이트된 키를 게시하는 것만으로 충분하지 않아요. 현실은 DNS 레코드에는 TTL이 있고 리졸버는 응답을 캐시한다는 것이에요. 클라이언트가 업데이트된 HTTPS 레코드를 조회하고 새 ECH 구성을 사용하기 시작하는 데 몇 분, 몇 시간, 심지어 며칠이 걸릴 수 있어요.
그런 까닭에 서버는 일정 기간 이전 ECH 구성을 계속 지원해야 해요. 그렇게 하지 않으면 서버 이름을 평문으로 대규모로 노출시킬 위험이 있어요. Caddy는 가끔 키를 회전하고 일정 시간 동안 회전된 키를 지원하다가 결국 버려요.
하지만 그것만으로는 충분하지 않을 수 있어요. 일부 클라이언트는 여러 이유로 업데이트된 키를 여전히 얻지 못하고, 그런 일이 생길 때마다 서버 이름이 노출될 위험이 있어요. 그래서 클라이언트에게 업데이트된 구성을 연결 인밴드(in band) 로 주는 다른 방법이 있어야 해요. 그것이 외부 이름(또는 공개 이름)의 용도예요.
공개 이름
"외부" ClientHello는 원본 서버만 아는 두 가지 미묘한 차이가 있는 일반 ClientHello예요:
- SNI 확장이 가짜
- ECH 확장이 진짜
그 "외부" SNI 확장은 여러분의 실제 도메인을 보호하는 공개 이름을 담고 있어요. 이 이름은 무엇이든 될 수 있지만, 서버가 공개 이름에 대해 권위적(authoritative)이어야 해요. Caddy가 그것에 대한 인증서를 얻을 것이기 때문이에요.
클라이언트가 ECH 연결을 시도하지만 서버가 내부 ClientHello를 해독하지 못하면, 외부 이름에 대한 인증서로 외부 ClientHello를 사용해 핸드셰이크를 실제로 완료할 수 있어요. 이 보안 연결은 엄격히 클라이언트에게 현재 ECH 구성을 보내는 데 만 사용돼요. 즉 초기 TLS 연결을 완료할 유일한 목적을 위한 임시 TLS 연결이에요. 애플리케이션 데이터는 전송되지 않아요: ECH 키만 전송돼요. 클라이언트가 업데이트된 키를 갖게 되면 의도한 대로 TLS 연결을 설정할 수 있어요.
이런 방식으로 실제 서버 이름은 보호된 채 유지되고, 동기화되지 않은 클라이언트도 연결할 수 있으며, 이 둘 모두 보안의 핵심 요소예요.
외부 이름은 여러분 사이트의 도메인 중 하나, 서브도메인, 또는 서버를 가리키는 다른 어떤 도메인 이름이어도 돼요. 정확히 하나의 일반 이름을 선택하는 것을 권장해요. 예를 들어 Cloudflare는 cloudflare-ech.com 뒤에서 수백만 개의 사이트를 서빙해요. 이는 익명성 집합의 크기를 늘리는 데 중요해요.
공개 이름은 비어 있으면 안 돼요. 즉 작동하려면 공개 이름이 구성되어야 해요. Caddy는 현재 이를 강제하지 않지만(나중에 할 수도 있음), ECH 사양은 공개 이름이 적어도 1바이트 길이여야 한다고 요구해요. 일부 소프트웨어는 빈 이름을 받아들이고 다른 소프트웨어는 그렇지 않아요. 이는 브라우저가 ECH를 사용하는데 서버가 유효하지 않다고 거부하거나, 구성이 DNS 레코드에 제대로 있는데 브라우저가 ECH를 사용하지 않는(유효하지 않기 때문에) 등의 혼란스러운 동작을 초래할 수 있어요. 적절한 ECH 구성과 게시를 보장하는 것은 사이트 소유자의 책임이에요.
익명성 집합
ECH의 프라이버시 이점을 최대화하려면 익명성 집합(anonymity set) 의 크기를 최대화하도록 노력해야 해요. 본질적으로 이 집합은 관찰자에게 동일한 동작을 보이는 클라이언트 지향 서버로 구성돼요. 아이디어는 관찰자가 클라이언트가 연결하는 가능한 사이트나 서비스를 쉽게 줄이거나 추론할 수 없게 하는 것이에요.
실제로 모든 사이트에 대해 공개 이름이 하나만 있는 것을 권장해요. (ECH 구성당 공개 이름이 1개이므로, 이는 주어진 시간에 활성 ECH 구성이 하나만 있다는 뜻이에요.) Caddy를 클러스터에서 운영한다면 Caddy가 다른 인스턴스와 ECH 구성을 자동으로 공유하고 조정하므로 이는 자동으로 처리돼요.
극단적으로 가면 이는 인터넷의 모든 사이트가 단일 IP 주소와 하나의 공개 이름 뒤에 있을 수 있거나 있어야 한다는 뜻이에요...
중앙화
... 그것이 다음 주제로 이어져요: 중앙화. ECH에 대한 비판 중 하나는 중앙화를 부추긴다는 것이에요. 적어도 두 가지 방식으로 그러는데, (1) 클라이언트가 DNS 조회에 DoH/DoT를 선호하게 해 모든 DNS 조회를 소수의 제공자를 통해 보내게 하고, (2) 규모에서 익명성 집합의 크기를 최대화함으로써요.
DoH나 DoT를 사용하면 DNS 조회가 모두 DoH/DoT 제공자를 통해 가요. 클라이언트와 제공자 사이의 DNS 데이터는 암호화되지만, 제공자와 DNS 서버 사이는 암호화되지 않아요. 전역 DoH/DoT는 본질적으로 모든 맛있는 평문 DNS 트래픽을 관찰이나 실패에 열려 있는 몇 개의 큰 파이프로 몰아넣어요.
비슷하게 규모에서 익명성 집합을 진정으로 최대화하면 모든 사이트가 cloudflare-ech.com 같은 단일 공개 이름 뒤에 보호될 거예요. 이는 프라이버시에 좋지만, 그러면 인터넷 전체가 Cloudflare와 그 도메인 이름 하나의 손에 달리게 돼요. 그 정도로 최대화하는 것은 필요하지도 실용적이지 않지만, 이론적 함의는 여전히 유효해요.
우리는 각 조직이나 개인이 모든 사이트에 대해 단일 이름을 선택해 사용하는 것을 권장하고, 대부분의 경우 그것으로 충분한 프라이버시를 제공해야 해요. 하지만 특정 사례에 대해서는 개별 위협 모델로 전문가와 상의하세요.
서브도메인 프라이버시
ECH를 사용하면 올바르게 배포된다면 사이드 채널로부터 서브도메인을 비밀로/사적으로 유지하는 것이 이제 이론적으로 가능해요.
대부분의 사이트는 서브도메인이 일반적으로 공개 정보이므로 이를 필요로 하지 않아요. 도메인 이름에 민감한 정보를 넣지 말 것을 권해요. 그렇다고 해도...
민감한 서브도메인이 인증서 투명성(CT) 로그로 누출되는 것을 피하려면 대신 와일드카드 인증서를 사용하세요. 즉 구성에 sub.example.com을 넣는 대신 *.example.com을 넣으세요. (중요한 정보는 와일드카드 인증서를 참고하세요.)
누출의 또 다른 원인은 대부분의 권위 있는 DNS 서버가 기본적으로 사용하는 DNSSEC예요. "zone walking"이라는 관행을 통해 NSEC 레코드를 보면 서브도메인 열거가 가능한데, NSEC 레코드는 존재에 대한 인증된 부정(authenticated denial of existence)을 제공하는 데 사용돼요. 이를 위해 알파벳 순서의 다음 사용 가능한 서브도메인을 가리켜 모든 레코드의 연결 리스트를 형성해요. 도메인이 최소한 NSEC3을 사용하거나 이상적으로는 와일드카드 CNAME 레코드를 사용해 이를 완화하세요.
그런 다음 Caddy에서 ECH를 활성화하세요. 와일드카드 인증서와 ECH, 와일드카드 CNAME 레코드를 결합하면, 연결을 시도하는 모든 클라이언트가 ECH를 사용하고 강력한 구현을 갖는 한 서브도메인을 제대로 숨겨야 해요. (프라이버시 보존은 여전히 클라이언트에 달려 있어요.)
ECH 활성화
제대로 작동하는 ECH는 DNS 레코드에 구성을 게시해야 하므로, DNS 제공자용 caddy-dns 모듈이 연결된 Caddy 빌드가 필요해요.
그런 다음 Caddyfile로 글로벌 옵션에 DNS 제공자 구성과 사용하려는 ECH 공개 이름을 지정해요:
{
dns <provider config...>
ech example.com
}
기억하세요:
- DNS 제공자 모듈이 연결되어 있어야 하고 제공자/계정에 대한 올바른 구성이 있어야 해요.
- ECH 공개 이름은 서버를 가리켜야 해요. Caddy가 그에 대한 인증서를 얻어요. 사이트의 도메인 중 하나일 필요는 없어요.
JSON을 사용한다면 tls 앱에 다음 속성을 추가하세요:
"encrypted_client_hello": {
"configs": [
{
"public_name": "example.com"
}
]
},
"dns": {
"name": "<provider name>",
// provider configuration
}
이 구성들은 ECH를 활성화하고 모든 사이트에 대한 ECH 구성을 게시해요. JSON 구성은 동작을 커스터마이즈하거나 고급 설정이 필요할 때 더 많은 유연성을 제공해요.
ECH 검증
ECH 주변에는 아직 도구가 많지 않아서, 작성 시점에 작동하는지 검증하는 가장 좋고 보편적인 방법은 Wireshark를 사용해 ServerName 필드에서 공개 이름을 찾는 것이에요.
먼저 서버를 시작하고 로그에 도메인에 대해 "published ECH configuration list" 같은 내용이 나오는지 확인하세요. (게시에 오류가 있으면 DNS 제공자 모듈이 libdns 1.0을 지원하는지 확인하고 문제가 있으면 제공자 저장소에 이슈를 올리세요.) Caddy는 공개 이름에 대한 인증서도 얻어야 해요.
다음으로 브라우저에 ECH가 활성화되어 있는지 확인하세요. 이는 DoH/DoT를 활성화해야 할 수 있어요. 새로 게시된 HTTPS 레코드를 가져올 수 있도록 브라우저(또는 시스템)의 DNS 캐시를 지우는 것도 좋아요. 기존 연결을 재사용하지 않도록 브라우저를 닫거나 최소한 새 비공개 탭을 여는 것도 권장해요.
그런 다음 Wireshark를 열고 적절한 네트워크 인터페이스에서 수신 대기를 시작하세요. Wireshark가 패킷을 수집하는 동안 브라우저에서 사이트를 로드하세요. 그러면 Wireshark를 일시 중지할 수 있어요. TLS ClientHello를 찾으면 실제로 연결한 도메인 이름이 아니라 공개 이름 이 ServerName 필드에 보여야 해요.
기억하세요: ECH를 사용하지 않아도 encrypted_client_hello 확장이 여전히 보일 수 있어요. 핵심 지표는 SNI 값이에요. ECH가 제대로 작동하면 Wireshark에서 진짜 사이트 이름을 평문으로 절대 보지 못해야 해요.
ECH 배포 문제가 발생하면 먼저 포럼에서 물어보세요. 버그라면 GitHub에 이슈를 올릴 수 있어요.
스토리지의 ECH
ECH 구성은 구성된 스토리지 모듈(기본은 파일 시스템)의 데이터 디렉터리에 있는 ech/configs 폴더 아래 저장돼요.
다음 폴더는 ECH 구성 ID로, 무작위로 생성되며 비교적 중요하지 않아요. 무작위성은 스펙이 지문 인식/추적을 완화하는 데 권장하는 것이에요.
메타데이터 사이드카 파일은 Caddy가 마지막 게시가 언제 발생했는지 추적하는 데 도움을 줘요. 이는 모든 구성 리로드에서 DNS 제공자를 두드리지 않도록 방지해요. 이 상태를 재설정해야 한다면 메타데이터 파일을 안전하게 삭제할 수 있어요. 하지만 이 또한 키가 회전될 때를 재설정할 수 있어요. 파일에 들어가 게시에 관한 정보만 지울 수도 있어요.