본문 바로가기
WIKI 기술 지식 베이스

자동 HTTPS

원문 보기 위키 갱신

자동 HTTPS (Automatic HTTPS)

Caddy는 HTTPS를 자동으로 그리고 기본으로 사용한 최초의 웹 서버예요.

자동 HTTPS는 모든 사이트의 TLS 인증서를 프로비저닝하고 갱신 상태로 유지해요. 또한 HTTP를 HTTPS로 리다이렉트해요! Caddy는 안전하고 현대적인 기본값을 사용해요 — 다운타임, 추가 구성, 별도 도구가 필요 없어요.

Caddy는 자동 HTTPS 기술을 혁신했어요. 2015년에 처음 실현 가능해진 첫날부터 이 작업을 해왔어요. Caddy의 HTTPS 자동화 로직은 세계에서 가장 성숙하고 견고해요.

다음은 작동 방식을 보여주는 28초짜리 영상이에요:

메뉴:

출처: Caddy 공식 문서

본문

Caddy는 HTTPS를 자동으로 그리고 기본으로 사용한 최초의 웹 서버예요.

자동 HTTPS는 모든 사이트의 TLS 인증서를 프로비저닝하고 갱신 상태로 유지해요. 또한 HTTP를 HTTPS로 리다이렉트해요! Caddy는 안전하고 현대적인 기본값을 사용해요 — 다운타임, 추가 구성, 별도 도구가 필요 없어요.

Caddy는 자동 HTTPS 기술을 혁신했어요. 2015년에 처음 실현 가능해진 첫날부터 이 작업을 해왔어요. Caddy의 HTTPS 자동화 로직은 세계에서 가장 성숙하고 견고해요.

다음은 작동 방식을 보여주는 28초짜리 영상이에요:

메뉴:

개요 (Overview)

기본적으로 Caddy는 모든 사이트를 HTTPS로 서빙해요.

  • Caddy는 IP 주소와 로컬/내부 호스트 이름을 로컬에서 자동으로 신뢰되는(허용된다면) 자체 서명 인증서로 HTTPS를 통해 서빙해요.
  • 예: localhost, 127.0.0.1
  • Caddy는 Let's Encrypt 또는 ZeroSSL 같은 공개 ACME CA의 인증서로 공개 DNS 이름을 HTTPS로 서빙해요.
  • 예: example.com, sub.example.com, *.example.com

Caddy는 모든 관리 인증서를 갱신 상태로 유지하고 HTTP(기본 포트 80)를 HTTPS(기본 포트 443)로 자동 리다이렉트해요.

로컬 HTTPS의 경우:

  • Caddy는 고유 루트 인증서를 트러스트 저장소에 설치하기 위해 비밀번호를 물어볼 수 있어요. 이는 루트당 한 번만 발생하며, 언제든 제거할 수 있어요.
  • Caddy의 루트 CA 인증서를 신뢰하지 않고 사이트에 접근하는 모든 클라이언트는 보안 오류를 표시해요.

공개 도메인 이름의 경우:

다음은 Caddy뿐 아니라 모든 기본 프로덕션 웹사이트의 일반적인 요구 사항이에요. 핵심 차이는 인증서를 프로비저닝할 수 있도록 DNS 레코드를 실행 전에 제대로 설정하는 거예요.

  • 도메인의 A/AAAA 레코드가 서버를 가리키고,
  • 포트 80과 443이 외부에서 열려 있고,
  • Caddy가 해당 포트에 바인딩할 수 있거나(또는 해당 포트가 Caddy로 전달되고),
  • 데이터 디렉터리가 쓰기 가능하고 영속적이며,
  • 도메인 이름이 구성의 관련 위치 어딘가에 나타난다면,

사이트는 자동으로 HTTPS로 서빙돼요. 그 외에 아무것도 할 필요 없어요. 그냥 작동해요!

HTTPS는 공유된 공개 인프라를 활용하므로, 서버 관리자로서 불필요한 문제를 피하고 발생 시 트러블슈팅하며 고급 배포를 제대로 구성할 수 있도록 이 페이지의 나머지 정보를 이해해야 해요.

활성화 (Activation)

Caddy는 서빙하는 도메인 이름(호스트 이름) 또는 IP 주소를 알게 되면 자동 HTTPS를 암시적으로 활성화해요. Caddy를 실행하거나 구성하는 방법에 따라 Caddy에 도메인/IP를 알리는 다양한 방법이 있어요:

다음 중 하나라도 있으면 자동 HTTPS가 전체적으로 또는 부분적으로 활성화되는 것을 막아요:

  • JSON 또는 Caddyfile로 명시적으로 비활성화
  • 구성에 호스트 이름이나 IP 주소를 제공하지 않음
  • HTTP 포트에서만 수신
  • Caddyfile에서 사이트 주소를 http://로 시작
  • 인증서를 수동으로 로드(ignore_loaded_certificates이 설정되지 않은 경우)

특수 사례:

  • .ts.net으로 끝나는 도메인은 Caddy가 관리하지 않아요. 대신 Caddy는 로컬에서 실행되는 Tailscale 인스턴스에서 핸드셰이크 시점에 이러한 인증서를 자동으로 가져오려 시도해요. 이를 위해서는 Tailscale 계정에서 HTTPS가 활성화되어야 하고, Caddy 프로세스는 루트로 실행 중이거나 tailscaled에서 인증서를 가져올 권한을 Caddy 사용자에게 부여하도록 구성해야 해요.

효과 (Effects)

자동 HTTPS가 활성화되면 다음이 발생해요:

자동 HTTPS는 명시적 구성을 절대 덮어쓰지 않으며, 단지 보강할 뿐이에요.

이미 HTTP 포트에서 수신 중인 서버가 있다면, HTTP→HTTPS 리다이렉트 경로는 호스트 매처와 함께 사용자의 경로 뒤에, 하지만 사용자 정의 catch-all 경로 앞에 삽입돼요.

필요하다면 자동 HTTPS를 커스터마이즈하거나 비활성화할 수 있어요. 예를 들어 특정 도메인 이름을 건너뛰거나 리다이렉트를 비활성화할 수 있어요(Caddyfile의 경우 글로벌 옵션으로).

호스트 이름 요구 사항

모든 호스트 이름(도메인 이름)은 다음 조건이면 완전 관리 인증서 자격이 있어요:

  • 비어 있지 않고
  • 영숫자, 하이픈, 점, 와일드카드(*)만으로 구성되며
  • 점으로 시작하거나 끝나지 않아야 해요(RFC 1034)

추가로 호스트 이름은 다음 조건이면 공개 신뢰 인증서 자격이 있어요:

  • localhost가 아니고(.localhost, .local, .internal, .home.arpa TLD 포함)
  • 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는 시스템의 로컬 트러스트 저장소에 설치하려 시도해요. 권한이 없다면 비밀번호를 물어봐요. 이 동작은 skip_install_trust[caddyfile 또는 "install_trust": false[json 구성으로 비활성화할 수 있어요. 권한 없는 사용자로 실행되어 실패하면 caddy trust를 실행해 권한 있는 사용자로 설치를 재시도할 수 있어요.

컴퓨터가 손상되지 않고 고유 루트 키가 유출되지 않는 한, 자체 머신에서 Caddy의 루트 인증서를 신뢰하는 것은 안전해요.

Caddy의 루트 CA가 설치된 후에는 로컬 트러스트 저장소에 "Caddy Local Authority"(다른 이름을 구성하지 않은 경우)로 표시돼요. 원한다면 언제든 제거할 수 있어요(caddy untrust 명령이 쉽게 만들어 줌).

인증서를 로컬 트러스트 저장소에 자동 설치하는 것은 편의를 위한 것이며, 특히 컨테이너를 사용하거나 권한 없는 시스템 서비스로 Caddy를 실행하는 경우 항상 작동한다는 보장은 없어요. 궁극적으로 내부 PKI에 의존한다면 Caddy의 루트 CA가 필요한 트러스트 저장소에 제대로 추가되도록 하는 것은 시스템 관리자의 책임이에요(이것은 웹 서버의 범위 밖).

CA 중간 인증서

중간 인증서와 키도 생성되며, 리프(개별 사이트) 인증서 서명에 사용돼요.

루트 인증서와 달리 중간 인증서는 수명이 훨씬 짧고 필요에 따라 자동으로 갱신돼요.

테스트 (Testing)

Caddy 구성을 테스트하거나 실험하려면 ACME 엔드포인트를 스테이징 또는 개발 URL로 변경해야 해요. 그렇지 않으면 요율 제한에 걸릴 가능성이 높고, 어떤 제한에 걸리느냐에 따라 최대 일주일 동안 HTTPS 접근이 차단될 수 있어요.

Caddy의 기본 CA 중 하나는 Let's Encrypt로, 같은 요율 제한을 받지 않는 스테이징 엔드포인트가 있어요:

https://acme-staging-v02.api.letsencrypt.org/directory

ACME 챌린지

공개 신뢰 TLS 인증서를 얻으려면 공개 신뢰를 받는 제3자 기관의 검증이 필요해요. 오늘날 이 검증 프로세스는 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는 도메인의 DNS 제공자에 접근하기 위한 자격 증명을 알아야 특수 TXT 레코드를 설정(및 제거)할 수 있어요. 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가 오랜 시간 동안 지수 백오프로 재시도할 수 있어요.

인증서 획득 또는 갱신에 오류가 발생하면 다음과 같이 처리돼요:

  1. Caddy는 우연일 수 있으니 잠시 멈췄다가 한 번 재시도해요.
  2. Caddy는 잠시 멈추고 다음 활성 챌린지 유형으로 전환해요.
  3. 모든 활성 챌린지 유형을 시도한 후, 다음 구성된 발급자를 시도해요: Let's Encrypt, ZeroSSL.
  4. 모든 발급자를 시도한 후 지수적으로 백오프해요.
    • 시도 사이 최대 1일.
    • 최대 30일 동안.

Let's Encrypt로 재시도하는 동안 Caddy는 요율 제한 우려를 피하기 위해 스테이징 환경으로 전환해요. 완벽한 전략은 아니지만 일반적으로 도움이 돼요.

ACME 챌린지는 최소 몇 초가 걸리며, 내부 요율 제한이 우발적 남용을 완화하는 데 도움을 줘요. Caddy는 사용자나 CA가 구성하는 것에 더해 내부 요율 제한을 사용해서, Caddy에 백만 개의 도메인 이름을 넘겨주면 점진적으로 — 하지만 가능한 한 빠르게 — 모두에 대한 인증서를 얻을 수 있게 해요. Caddy의 내부 요율 제한은 현재 ACME 계정당 10초에 10회 시도예요.

리소스 누출을 피하기 위해 Caddy는 구성이 변경되면 진행 중인 작업(ACME 트랜잭션 포함)을 중단해요. Caddy가 빈번한 구성 재로드를 처리할 수 있지만, 이러한 운영 고려 사항에 주의하고 구성 변경을 배치로 묶어 재로드를 줄이고 Caddy가 백그라운드에서 인증서 획득을 실제로 마칠 기회를 주는 것을 고려해요.

발급자 폴백 (Issuer fallback)

Caddy는 인증서를 성공적으로 얻을 수 없을 때 다른 CA로 완전히 중복된 자동 장애 조치를 지원하는 첫 번째(그리고 지금까지 유일한) 서버예요.

기본적으로 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으로 정의된 사이트는 sub.example.com에 대한 인증서를 관리하게 하고, *.example.com으로 정의된 사이트는 *.example.com에 대한 와일드카드 인증서를 관리하게 해요. 이는 Common Caddyfile Patterns 페이지에서 확인할 수 있어요. 다른 동작이 필요하면 JSON 구성에서 인증서 주체와 사이트 이름("호스트 매처")을 더 정밀하게 제어할 수 있어요.

Caddy 2.10부터 와일드카드 인증서를 자동화할 때 Caddy는 구성의 개별 서브도메인에 와일드카드 인증서를 사용해요. 명시적으로 구성하지 않으면(force_automate로) 개별 서브도메인에 대한 인증서를 얻지 않아요.

와일드카드 인증서는 광범위한 권한을 나타내므로, 서브도메인이 너무 많아 개별 인증서를 관리하는 것이 PKI에 부담이 되거나 CA가 시행하는 요율 제한에 걸릴 때, 또는 키 손상 시 DNS 영역의 그만큼을 노출시키는 위험을 감수할 만큼 개인정보 보호 트레이드오프가 가치 있을 때만 사용해야 해요. 와일드카드 인증서만으로 특정 서브도메인을 숨기는 개인정보 보호를 제공하지는 않는다는 점을 유의해요. Encrypted ClientHello(ECH)가 활성화되지 않으면 여전히 TLS ClientHello 패킷에 노출돼요. (아래 참조.)

참고: Let's Encrypt는 와일드카드 인증서를 얻으려면 DNS 챌린지를 요구해요.

암호화 ClientHello (ECH)

일반적으로 TLS 핸드셰이크는 서버 이름 표시(SNI, 연결 대상 도메인)를 포함한 ClientHello를 평문으로 보내는 것을 수반해요. 핸드셰이크 이후의 연결을 암호화하는 데 필요한 매개변수를 포함하기 때문이에요. 이는 당연히 ClientHello에서 가장 민감한 부분인 도메인 이름을, 물리적으로 가까이 있지 않더라도 연결을 엿들을 수 있는 모든 사람에게 노출해요. 대상 IP가 많은 다른 사이트를 서빙할 때 어떤 서비스에 연결하는지 드러나며, 일부 정부가 인터넷을 검열하는 방식이기도 해요.

암호화 ClientHello를 사용하면 클라이언트는 진짜 ClientHello를 "내부" ClientHello를 복호화하기 위한 매개변수를 설정하는 "외부" ClientHello로 감싸 도메인 이름을 보호할 수 있어요. 그러나 이 작업이 작동하고 실제 개인정보 보호 이점을 제공하려면 많은 움직이는 부품이 완벽하게 함께 맞아야 해요.

먼저 클라이언트는 ClientHello를 암호화하는 데 사용할 매개변수 또는 구성을 알아야 해요. 이 정보에는 공개 키와 "외부" 도메인("공개 이름") 등이 포함돼요. 이 구성은 신뢰할 수 있는 방식으로 게시되거나 배포되어야 해요.

이론적으로 종이에 적어 모든 사람에게 나눠줄 수 있지만, 대부분의 주요 브라우저는 사이트에 연결할 때 ECH 매개변수를 포함하는 HTTPS 유형 DNS 레코드를 조회하는 것을 지원해요. 따라서 (1) ECH 구성(공개/개인 키 쌍 및 기타 매개변수)을 생성한 다음 (2) base64로 인코딩된 ECH 구성을 포함하는 HTTPS 유형 DNS 레코드를 만들어야 해요.

아니면... Caddy가 그 모든 것을 대신 해주게 할 수 있어요. Caddy는 ECH 구성을 자동으로 생성, 게시, 서빙할 수 있는 첫 번째이자 유일한 웹 서버예요.

HTTPS 레코드가 게시되면 클라이언트는 사이트에 연결할 때 ECH 구성을 위해 HTTPS 레코드에 대한 DNS 조회를 수행해야 해요. 일반적으로 DNS 조회는 평문이라 결과적인 ECH 핸드셰이크의 보안을 손상시키므로, 브라우저는 DNS-over-HTTPS(DoH)나 DNS-over-TLS(DoT) 같은 보안 DNS 프로토콜을 사용해야 해요. 브라우저에 따라 수동으로 활성화해야 할 수 있어요.

클라이언트가 ECH 구성을 안전하게 다운로드하면 포함된 공개 키를 사용해 ClientHello를 암호화하고 사이트에 연결을 진행해요. 그러면 Caddy가 내부 ClientHello를 복호화하고 도메인 이름이 와이어에 평문으로 나타나지 않은 채 사이트를 서빙해요.

배포 고려 사항

ECH는 미묘한 기술이에요. Caddy가 ECH를 완전히 자동화한다 해도 최대 개인정보 보호 이점을 위해 많은 것을 고려해야 해요. 또한 다양한 트레이드오프를 알고 있어야 해요.

게시 (Publication)

Caddy는 도메인에 대한 레코드가 이미 있는 경우에만 HTTPS 레코드를 만들어요. 이는 와일드카드가 커버할 수 있는 서브도메인의 DNS 조회가 깨지는 것을 방지해요. 사이트에 서버를 가리키는 A/AAAA 레코드가 최소한 있는지 확인해요. DNS 레코드에 와일드카드만 사용한다면 와일드카드 도메인이 Caddy 구성에도 나타나야 해요.

Caddy는 CNAME 레코드가 있는 도메인에 대해 HTTPS 레코드를 게시하지 않아요.

ECH GREASE

Wireshark를 열고 주요 브라우저(예: Firefox 또는 Chrome)의 현대 버전에서 어떤 사이트에나(ECH를 지원하지 않는 사이트라도, ECH가 비활성화된 상태라도) 연결하면 핸드셰이크에 encrypted_client_hello 확장이 포함된 것을 볼 수 있어요.

그 목적은 진짜 ECH 핸드셰이크를 평문 핸드셰이크와 구분할 수 없게 만드는 거예요. ECH 핸드셰이크가 일반 핸드셰이크와 다르게 보이면 검열자는 최소한의 여파/부수 피해로 ECH 핸드셰이크를 차단할 수 있으니까요. 하지만 그럴듯한 ECH 확장이 있는 모든 핸드셰이크를 차단하면 본질적으로 인터넷 대부분을 꺼버리게 돼요. (목표는 광범위한 검열의 비용을 높이는 거예요.)

이것은 주로 연결을 트러블슈팅할 때 알아야 할 중요 사항이에요.

키 교체 (Key rotation)

인증서 키처럼 같은 키를 오래 사용하는 것은 좋은 관행이 아니며(그리고 완전히 안전하지 않을 수 있음) 그래서 ECH 키는 정기적으로 교체해야 해요. 인증서와 달리 ECH 구성은 엄격히 만료되지 않지만, 서버는 그래도 교체해야 해요.

키 교체는 까다로워요. 클라이언트가 업데이트된 키를 알아야 하기 때문이에요. 서버가 단순히 이전 키를 새 키로 교체하면, 클라이언트가 새 키에 대해 즉시 통보받지 않는 한 모든 ECH 핸드셰이크가 실패할 거예요. 하지만 단순히 업데이트된 키를 게시하는 것만으로는 부족해요. 현실은 DNS 레코드에 TTL이 있고 리졸버가 응답을 캐시하는 등 클라이언트가 업데이트된 HTTPS 레코드를 조회하고 새 ECH 구성을 사용하기 시작하는 데 몇 분, 몇 시간, 심지어 며칠이 걸릴 수 있어요.

그 이유로 서버는 한동안 이전 ECH 구성을 계속 지원해야 해요. 그렇게 하지 않으면 규모에 맞춰 서버 이름이 평문으로 노출될 위험이 있어요. Caddy는 때때로 키를 교체하고, 결국 폐기될 때까지 교체된 키를 한동안 지원해요.

하지만 그것으로 충분하지 않을 수 있어요. 일부 클라이언트는 다양한 이유로 업데이트된 키를 여전히 받지 못하며, 그런 일이 발생할 때마다 서버 이름이 노출될 위험이 있어요. 따라서 클라이언트에게 in band(연결과 함께)로 업데이트된 구성을 제공할 또 다른 방법이 필요해요. 그것이 외부 이름(또는 공개 이름)의 용도예요.

공개 이름 (Public name)

"외부" ClientHello는 원본 서버만 아는 두 가지 미묘한 차이점이 있는 일반 ClientHello예요:

  1. SNI 확장이 가짜임.
  2. ECH 확장이 진짜임.

그 "외부" SNI 확장은 실제 도메인을 보호하는 공개 이름을 포함해요. 이 이름은 무엇이든 될 수 있지만, 서버가 공개 이름에 대해 권위적이어야 해요. Caddy가 이에 대한 인증서를 얻을 것이기 때문이에요.

클라이언트가 ECH 연결을 시도하지만 서버가 내부 ClientHello를 복호화할 수 없으면, 실제로 외부 이름에 대한 인증서를 사용해 외부 ClientHello로 핸드셰이크를 완료할 수 있어요. 이 보안 연결은 엄격히 오직 클라이언트에게 현재 ECH 구성을 보내는 데만 사용돼요. 즉, 초기 TLS 연결을 완료하는 유일한 목적의 임시 TLS 연결이에요. 애플리케이션 데이터는 전송되지 않아요: ECH 키만 전송돼요. 클라이언트가 업데이트된 키를 얻으면 의도한 대로 TLS 연결을 설정할 수 있어요.

이런 방식으로 진짜 서버 이름은 보호되고 동기화되지 않은 클라이언트는 계속 연결할 수 있어요. 이 둘 다 보안의 핵심 요소예요.

외부 이름은 사이트의 도메인 중 하나, 서브도메인, 또는 서버를 가리키는 다른 도메인 이름일 수 있어요. 정확히 하나의 일반 이름을 선택하는 것을 권장해요. 예를 들어 Cloudflare는 cloudflare-ech.com 뒤에서 수백만 개의 사이트를 서빙해요. 이는 익명 집합의 크기를 키우는 데 중요해요.

공개 이름은 비어 있으면 안 돼요. 즉, 작업이 되려면 공개 이름이 구성되어야 해요. Caddy는 현재 이를 강제하지 않지만(나중에 할 수 있음), ECH 사양은 공개 이름이 최소 1바이트여야 한다고 요구해요. 일부 소프트웨어는 빈 이름을 받아들이고 다른 소프트웨어는 받아들이지 않아요. 이는 브라우저가 ECH를 사용하지만 서버가 유효하지 않다고 거부하거나, 구성이 DNS 레코드에 제대로 있어도 브라우저가 (유효하지 않아서) ECH를 사용하지 않는 것 같은 혼란스러운 동작을 유발할 수 있어요. 적절한 ECH 구성과 게시로 개인정보를 보장하는 것은 사이트 소유자의 책임이에요.

익명 집합 (Anonymity set)

ECH의 개인정보 보호 이점을 최대화하려면 익명 집합의 크기를 최대화하도록 노력해요. 본질적으로 이 집합은 관찰자에게 동일한 동작을 보이는 클라이언트 대상 서버로 구성돼요. 아이디어는 관찰자가 클라이언트가 연결하는 가능한 사이트나 서비스를 쉽게 줄이거자 추론할 수 없게 하는 거예요.

실제로는 모든 사이트에 대해 공개 이름 하나만 두는 것을 권장해요. (ECH 구성당 공개 이름은 1개이므로, 이는 특정 시점에 활성 ECH 구성이 1개만 있다는 의미예요.) 클러스터에서 Caddy를 운영하면 Caddy가 다른 인스턴스와 ECH 구성을 자동으로 공유하고 조정하므로 이 문제를 알아서 처리해줘요.

극단으로 가면 이는 인터넷의 모든 사이트가 단일 IP 주소와 하나의 공개 이름 뒤에 있을 수 있거나 그래야 한다는 것을 의미해요...

중앙화 (Centralization)

...이것이 다음 주제인 중앙화로 이어져요. 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 레코드를 보고 서브도메인 열거가 가능해요. 이를 위해 모든 레코드의 연결 리스트를 형성하면서 알파벳 순서로 다음 사용 가능한 서브도메인을 가리켜요. 도메인이 최소한 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 제공자를 때리지 않게 방지해요. 이 상태를 재설정해야 한다면 메타데이터 파일을 안전하게 삭제할 수 있어요. 하지만 키가 교체되는 시간도 재설정될 수 있어요. 파일에 들어가 게시에 대한 정보만 지울 수도 있어요.

더 알아보기 (Learn more)