The certificate was renewed, but the browser still showed the old one
One redirect line blocked renewal
한국어 원문으로 표시합니다.
목표
HTTP-01 챌린지를 파드 안에서 통과시키고, HTTP→HTTPS 리다이렉트가 챌린지를 먹어 갱신이 막히는 사고를 재현한 뒤, 챌린지 경로만 예외로 두어 고칩니다. 상시 확인용 probe.sh 와 cert-manager Certificate 매니페스트를 작성합니다.
왜 중요한가
ACME 자동 갱신은 첫 발급이 성공하면 그 뒤 두 달 동안 아무 증상이 없습니다. 그 사이에 넣은 리다이렉트 한 줄이 갱신 시점에 챌린지를 막고, 그때는 아무도 그 변경을 떠올리지 못합니다. 홈랩에서 실제로 겪은 사고이고, 재현하면 한눈에 보입니다. 재료는 둘입니다 — /opt/app/edge.py 는 설정 파일(/root/acme/edge.json)을 요청마다 다시 읽는 엣지(HTTP 8080·HTTPS 8443)이고, /opt/app/acme_va.py 는 리다이렉트를 따라가며 본문을 키 인가와 비교하는 검증 서버입니다.
단계
- 토큰
zk3r9QmZ7Vf1Yb2Ld8Ns4Hj6Xc5Pa0Wt, 계정 키 지문Q7pVw2xK9mN4rT1sB6yL0cH3fJ8dG5eA입니다./root/acme/challenges/zk3r9QmZ7Vf1Yb2Ld8Ns4Hj6Xc5Pa0Wt파일에 키 인가 문자열을 넣으세요. /root/acme/edge.json을 만드세요 —http_port8080,https_port8443,redirect_to_httpsfalse,exempt_paths에/.well-known/acme-challenge/,challenge_dir/root/acme/challenges.python3 /opt/app/edge.py --config /root/acme/edge.json을 띄우고curl -s http://127.0.0.1:8080/.well-known/acme-challenge/<토큰>의 본문을/root/acme/02-fetch.txt에 저장하세요.python3 /opt/app/acme_va.py --url http://127.0.0.1:8080/.well-known/acme-challenge/<토큰> --expect <키인가>의 출력을/root/acme/03-valid.txt에 저장하세요. 첫 줄이VALID여야 합니다.- 사고 재현:
edge.json에서redirect_to_https를 true 로,exempt_paths를 빈 목록으로 바꾸세요.curl -sI http://127.0.0.1:8080/.well-known/acme-challenge/x의 출력을/root/acme/04-curl.txt에, 검증 서버의 출력을/root/acme/04-broken.txt에 저장하세요(INVALID 여야 합니다). - 고치기: 리다이렉트는 켜 둔 채
exempt_paths에/.well-known/acme-challenge/를 다시 넣으세요. 검증 서버 출력을/root/acme/05-fixed.txt에(VALID),curl -sI http://127.0.0.1:8080/을/root/acme/05-app.txt에(여전히 301) 저장하세요. /root/acme/probe.sh <http://host:port>를 만드세요.<base>/.well-known/acme-challenge/probe-check의 상태 코드가 3xx 면REDIRECT를 출력하고 1, 그 밖(200·404 등)이면OK를 출력하고 0 으로 끝나야 합니다.http://127.0.0.1:8080에 대한 출력을/root/acme/06-probe.txt에 저장하세요./root/acme/certificate.yaml에 cert-managerCertificate를 쓰세요 —apiVersion: cert-manager.io/v1,spec.secretName,spec.dnsNames에www.lab.internal,spec.duration과spec.renewBefore(시간 단위h),spec.issuerRef.kind: ClusterIssuer./root/acme/08-report.md에 사고를 정리하세요.HTTP-01, 증거로 본301, 예외로 둔 경로.well-known/acme-challenge, 그리고renewBefore가 왜 사고를 늦게 드러내는지가 들어가야 합니다.
참고
- edge.json 은 요청마다 다시 읽히므로 고친 뒤 엣지를 재기동할 필요가 없습니다.
- 검증 서버는 실패해도 출력을 냅니다.
> 파일로 저장하면 종료 코드와 무관하게 남습니다. curl -sI는 HEAD 요청으로 상태줄과 헤더만 보여 줍니다.Location:헤더가 어디로 가는지 보세요.- 흔한 실수: 4단계에서
exempt_paths를 비우지 않으면 사고가 재현되지 않습니다. 7단계에서renewBefore: 15d처럼d를 쓰면 cert-manager 는 받아 주지 않습니다.
키 인가 파일 두기
/root/acme/challenges/zk3r9QmZ7Vf1Yb2Ld8Ns4Hj6Xc5Pa0Wt 에 키 인가 문자열(토큰.지문)을 넣으세요.
RFC 8555 의 키 인가는 token, 마침표, 계정 키 지문을 이은 문자열입니다. 파일 이름은 토큰 그대로, 내용은 그 한 줄입니다. 지문은 지시문에 주어져 있습니다.
엣지 띄우고 챌린지 받기
/root/acme/edge.json(리다이렉트 off, 챌린지 경로 예외)을 만들어 엣지를 띄우고, 챌린지 URL 의 본문을 /root/acme/02-fetch.txt 에 저장하세요.
JSON 키 이름은 지시문 그대로입니다. 엣지는 nohup 으로 뒤에 두고 로그는 /root/acme/edge.log 로 보내세요. curl -s 로 받은 본문이 1단계의 키 인가와 같아야 합니다.
검증 서버에게 확인받기
acme_va.py 를 돌려 출력을 /root/acme/03-valid.txt 에 저장하세요. 첫 줄이 VALID 여야 합니다.
--url 은 챌린지 URL, --expect 는 키 인가 문자열입니다. 출력 첫 줄이 판정이고 그 아래 따라간 경로가 한 줄씩 나옵니다.
리다이렉트가 챌린지를 먹는다
edge.json 을 리다이렉트 on·예외 없음으로 바꾸고, curl -sI 출력을 /root/acme/04-curl.txt 에, 검증 서버 출력을 /root/acme/04-broken.txt 에 저장하세요.
python3 한 줄로 JSON 을 고치면 실수가 적습니다. curl -sI 의 첫 줄에 301, Location 헤더에 https 가 보이고, 검증 서버는 301 → 404 를 따라간 끝에 INVALID 를 냅니다.
챌린지 경로만 예외로
리다이렉트는 켜 둔 채 exempt_paths 에 챌린지 경로를 넣고, 검증 출력을 /root/acme/05-fixed.txt 에, curl -sI http://127.0.0.1:8080/ 을 /root/acme/05-app.txt 에 저장하세요.
고치는 것은 예외 목록 한 줄입니다. 앱 경로(/)는 여전히 301 로 HTTPS 에 가야 하고, 챌린지 경로만 HTTP 에서 바로 200 을 줘야 합니다.
갱신이 막혔는지 보는 한 줄
/root/acme/probe.sh <http://host:port> 를 만들고(3xx 면 REDIRECT·1, 아니면 OK·0) http://127.0.0.1:8080 의 출력을 /root/acme/06-probe.txt 에 저장하세요.
curl -s -o /dev/null -w '%{http_code}' 로 상태 코드만 받아 case 로 가르면 됩니다. 404 는 OK 입니다 — 파일이 없는 것은 갱신을 막지 않고, 리다이렉트만 막습니다. 채점기는 301 서버와 404 서버를 하나씩 띄워 돌려 봅니다.
cert-manager 로 선언하기
/root/acme/certificate.yaml 에 Certificate(cert-manager.io/v1, secretName, dnsNames 에 www.lab.internal, duration·renewBefore 는 h 단위, issuerRef.kind ClusterIssuer)를 쓰세요.
문서의 예시 구조를 따르되 값은 이 실습의 것으로. duration 은 renewBefore 보다 커야 하고 둘 다 Go 시간 문자열(h·m·s)입니다. 다른 네임스페이스에서도 쓰는 발급자는 ClusterIssuer 입니다.
사고 보고서
/root/acme/08-report.md 에 HTTP-01 이 막힌 경위, 증거(301), 예외로 둔 경로, renewBefore 가 사고를 늦게 드러내는 이유를 적으세요.
4단계의 curl 결과가 증거이고 5단계의 예외 목록이 고침입니다. 발급 직후에는 증상이 없고 renewBefore 시점에야 챌린지가 다시 도는 것이 '늦게 드러나는' 이유입니다.