이제 인증서 갱신 시점은 CA가 정한다

2020년 Let's Encrypt는 인증서 300만 장을 폐기해야 했지만 '갱신하라'고 알릴 방법이 없었다. 이제 RFC 9773이 된 ACME Renewal Information이 당신 스케줄에 CA의 다이얼을 달았다.

2020년 2월 28일 저녁, Let’s Encrypt는 CAA 레코드 — 어느 인증기관이 특정 도메인에 발급할 수 있는지를 명시하는 DNS 레코드 — 를 확인하는 코드에서 버그를 발견했다. 인증서에 담긴 모든 이름을 발급 직전에 다시 확인해야 하는데, 어떤 경우엔 이름 하나만 다시 확인하고 나머지는 그냥 도장을 찍었던 것이다. 도메인 통제권 자체는 정당했으니 그 인증서들은 ‘멀쩡’했다. 하지만 CA가 자기 규칙을 지켰느냐로 따지면 멀쩡하지 않았다. 그래서 없애야 했다.

숫자는 3,048,289장이었다 — 당시 활성 상태이던 약 1억 1,600만 장의 Let’s Encrypt 인증서 중 약 2.6%. 폐기는 3월 4일 20:00 UTC로 정해졌고, 인터넷에는 300만 장을 교체할 시간이 닷새가 채 안 되게 주어졌다. 그리고 핵심은 여기다: 그 일을 성사시킬 Let’s Encrypt의 유일한 수단은 블로그 글 하나와 쏟아낸 이메일 뭉치뿐이었다. 버튼은 없었다. 인증서 발급을 API 호출 한 번으로 자동화한 CA가, 300만 대의 서버에 “당신이 돌리는 그것이 곧 폐기됩니다 — 지금 갱신하세요”라고 알릴 자동화된 방법은 없었다.

그들은 기한을 못 맞췄다. 100만 장이 넘는 인증서가 제때 교체될 기미조차 없어서, Let’s Encrypt는 서류상 실수로 100만 개 사이트를 깨뜨리느니 그것들의 폐기를 미뤘다. 폐기는 대체로 진행됐고 웹은 대체로 살아남았지만, 이 사건은 ‘완전 자동화’된 인증서 생태계가 실제로 얼마나 자동화돼 있었는지에 대해 민망한 사실을 드러냈다. 영원히 스케줄에 맞춰 일어나야 하는 유일한 작업인 갱신 — 그게 바로 CA가 손 닿지 못하는 부분이었다.

갱신 시점은 누가 정하고 있었나

이전까지 갱신 날짜는 당신 클라이언트가 하는 추측이었다. Certbot을 비롯한 ACME 클라이언트는 시점을 고른다 — 흔히 인증서 수명의 3분의 2를 지난 때, 또는 만료 며칠 전 고정값 — 그리고 그때 갱신한다. 합리적인 휴리스틱이지만 거대한 사각지대가 하나 있다: 정적이라는 것. 클라이언트는 인증서에 찍힌 만료일을 근거로 한 번 결정하고, 그 뒤 무슨 일이 벌어져도 마음을 바꾸지 않는다. CA가 당신 인증서를 잘못 발급했다는 사실은 인증서에 찍혀 있지 않다. 고객 절반이 매월 1일 UTC 새벽 3시에 갱신한다는 사실도 — 다들 그때 cron이 돌기 때문에 — 찍혀 있지 않다.

그래서 같은 옷을 입은 문제가 둘 있었다. 극적인 쪽은 폐기였다: “지금 갱신하라, 뭔가 잘못됐다”를 전할 채널이 없음. 지루한 쪽은 부하였다: 수백만 클라이언트가 제각기 고른 스케줄로 갱신하는데, 그게 스파이크로 뭉쳐 예측 가능한 순간마다 CA 인프라를 두들긴다 — 아무도 조율하지 않았다는 것 말고는 이유도 없이. 둘은 같은 빠진 조각이다 — CA는 당신이 언제 갱신해야 하는지에 대해 의견이 있는데, 그걸 말할 방법이 없다.

ARI가 실제로 더하는 것

ACME Renewal Information은 2025년 RFC 9773으로 발행됐고, 바로 그 빠진 채널이다. 그리고 거의 공격적일 만큼 단순하다. CA는 새 리소스 renewalInfo를 노출한다. 당신 클라이언트는 지금 들고 있는 인증서에서 파생한 식별자를 그 엔드포인트로 평범한 GET 요청에 실어 보낸다. CA는 suggestedWindowstartend 두 타임스탬프 — 를 담은 작은 JSON 객체로 답하며 “이 인증서를 이 구간 어딘가에서 갱신하라”고 말한다. CA가 이유를 설명할 페이지를 가리키고 싶으면 선택적 explanationURL이, 클라이언트가 얼마나 자주 다시 확인할지 알려주는 Retry-After 헤더가 붙는다.

클라이언트가 할 일은 그 구간 안에서 균일 무작위 시점을 하나 골라 그때 갱신하는 것이다. 제안된 시점이 이미 과거면 즉시 갱신한다. 프로토콜은 이게 전부다. 새 암호도, 핸드셰이크 변경도, 인증서 포맷 갈아엎기도 없다 — 주기적인 질문(“이거 언제 갱신할까?”) 하나와, 이제 CA가 줄 수 있게 된 답변 하나뿐.

무작위성이 눈앞에 숨은 부하 해법이다. 모든 클라이언트가 똑같은 결정론적 오프셋 대신 CA가 준 구간 안에서 무작위 시점을 고르면, 갱신 스파이크는 매끄러운 곡선으로 펴진다. Let’s Encrypt는 이미 ARI를 프로덕션에서 돌리고 정확히 이렇게 매일 쓴다: 갱신을 자기 인프라 전체에 흩뿌리는 구간을 나눠줘서 어느 한 순간도 몰리지 않게 한다. 당신은 신경 쓸 필요가 없고, 그게 요점이다.

그리고 폐기 해법

극적인 경우는 같은 메커니즘에서 구간을 앞으로 밀어붙인 것뿐이다. 규정 위반 사건이나 오발급이 벌어지면 CA는 영향받은 인증서의 제안 구간을 지금으로 좁힌다. renewalInfo를 폴링하는 클라이언트 — 어차피 평소 스케줄대로 하는 — 는 과거에 시작하는 구간을 보고 즉시 갱신한다. 2020년 대소동의 미래판은 닷새짜리 비상사태라기보다 이렇게 보인다: CA가 구간을 뒤집고, 클라이언트가 이후 몇 시간에 걸쳐 새 인증서를 집어 들고, 그다음에 옛것이 폐기된다 — 이미 교체된 뒤에.

그 순서 뒤집기가 여기서 벌어진 조용한 혁명이다. 10년 동안 업계는 폐기를 살아 있는 인증서에 가해지는 무언가로 작동시키려 했다 — OCSP가 핸드셰이크 도중 CA에 묻고, CRL이 죽은 시리얼을 나열하고 — 그리고 그 전부가 조용히·소리 없이 실패했는데, 그건 아예 없는 것보다 나쁘다. ARI는 이 문제 전체를 비껴간다. 지구상 모든 브라우저에 “이 인증서 더는 믿지 마”라고 말하려 애쓰는 대신, 그것을 들고 있는 훨씬 작은 서버 집합에 “이 인증서 지금 교체해”라고 말하고, 폐기는 실시간으로 지고 있는 경주가 아니라 사후 정리 단계가 된다.

당신이 하는 거래

무엇이 바뀌었는지 이름 붙일 가치가 있다. “그냥 ARI 켜”는 이걸 과소평가하니까. 당신 인증서의 갱신 주기는 예전엔 당신 클라이언트의 결정이었다 — 인증서 자체의 공개 정보로 내리는. 이제 그건 부분적으로 CA의 결정이고, 당신 클라이언트가 따르도록 기대되는 신호로 전달된다. Certbot은 최근 버전에서 ARI를 기본으로 존중하는데, 많은 사람에게 이건 이미 상의 없이 벌어진 일이라는 뜻이다. 당신은 인프라 타이밍의 다이얼을 제3자 — 잘 운영되고 이유도 타당한 제3자지만, 어쨌든 제3자 — 에게 넘겼다.

나는 그게 옳은 거래이고, 아슬아슬한 판단도 아니라고 본다. 대안 — 모든 운영자가 정적인 날짜로 갱신 시점을 추측하며 발밑에서 아무것도 안 바뀌길 바라는 것 — 이야말로 2020년에 평범한 버그를 300만 장짜리 화재 훈련으로 만든 바로 그것이다. 그리고 어차피 곧 선택의 문제가 아니게 된다: 인증서 수명은 2029년까지 47일을 향해 행진 중이고, 이는 갱신이 인증서가 1년 가던 시절보다 대략 여덟 배 자주 일어난다는 뜻이다. 그 주기에서 조율 안 된 갱신은 성가심이 아니라 당신 CA를 향한 자해성 DDoS다. 누군가는 스케줄을 쥐어야 한다. ARI는 그게 CA여야 한다고 소리 내어 인정하고, 그렇게 말할 방법을 줄 뿐이다.

토론 참여

← 블로그로 돌아가기