docs: D-4 — the certificate is fine and the renewal itself went untested
Three SAN entries and no wildcard is the constraint that cost something real in B-7, where oauth2-proxy had to borrow Grafana's app2 hostname because a fourth name was not available. The served chain is four deep and verifies, so fullchain.pem is configured rather than the cert.pem mistake that only breaks clients without a cached intermediate. The forced renewal and the reload behaviour could not be measured because sudo on the host asks for a password, the same silent failure first noticed in B-7. nginx reload is graceful by design, but this lab has repeatedly shown that by design is not the same as measured, so it is recorded as untested rather than assumed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
027c24ee27
commit
4864d837f1
@@ -0,0 +1,182 @@
|
||||
# D-4 — 인증서 갱신
|
||||
|
||||
브랜치 `feature/keycloak-d4-certificate-renewal` ·
|
||||
증거 [`docs/evidence/d4-certificate-renewal/`](evidence/d4-certificate-renewal/) ·
|
||||
2026-09-04 17:25–17:35 KST
|
||||
|
||||
---
|
||||
|
||||
## 0. 결론부터
|
||||
|
||||
| 확인 | 결과 |
|
||||
|---|---|
|
||||
| 인증서 구성 | **SAN 3개** (`auth`/`app1`/`app2`), 와일드카드 아님 |
|
||||
| 체인 완결성 | **정상.** `Verify return code: 0 (ok)`, 4단계 |
|
||||
| 갱신 자동화 | **동작 중.** `certbot-renew.timer` enabled·active, 11시간 전 실행됨 |
|
||||
| 남은 기간 | **88일** (갱신까지 약 58일) |
|
||||
| **강제 갱신 실측** | **★ 못 했다.** `sudo: a password is required` |
|
||||
|
||||
**측정한 것과 못 한 것을 나눠 적는다.** 못 한 것을 안 한 것처럼 쓰면
|
||||
이 기록 전체의 신뢰가 깎인다.
|
||||
|
||||
---
|
||||
|
||||
## 1. 인증서 구성 — B-7 에서 실제로 걸린 제약
|
||||
|
||||
```
|
||||
X509v3 Subject Alternative Name:
|
||||
DNS:app1.hyeonworks.com, DNS:app2.hyeonworks.com, DNS:auth.hyeonworks.com
|
||||
```
|
||||
|
||||
**세 이름뿐이고 와일드카드가 아니다.**
|
||||
|
||||
> **이 제약이 B-7 에서 실제 비용을 만들었다.**
|
||||
> oauth2-proxy 를 올릴 호스트명이 없어 **Grafana 가 쓰던 `app2` 를 빌려야 했고**,
|
||||
> 그 때문에 관측 스택의 웹 UI 가 실험 동안 내려가 있었다.
|
||||
>
|
||||
> **"인증서에 이름을 몇 개 넣을 것인가" 는 TLS 설정이 아니라
|
||||
> 나중에 무엇을 배포할 수 있는가를 정하는 결정이다.**
|
||||
|
||||
| | 이 실험대 | 와일드카드였다면 |
|
||||
|---|---|---|
|
||||
| 새 호스트 추가 | **인증서 재발급 필요** | 바로 가능 |
|
||||
| DNS-01 검증 | 필요 | 필요 (와일드카드는 DNS-01 만 가능) |
|
||||
| 노출 | 이름 3개만 | **하위 전체가 한 키에 묶인다** |
|
||||
|
||||
---
|
||||
|
||||
## 2. 체인이 완전한가 — 흔한 실수 확인
|
||||
|
||||
```
|
||||
0 s:CN = auth.hyeonworks.com ← 리프
|
||||
1 s:C = US, O = Let's Encrypt, CN = YE2 ← 중간
|
||||
2 s:C = US, O = ISRG, CN = Root YE
|
||||
3 s:C = US, O = Internet Security Research Group, CN = ISRG Root X2
|
||||
Verify return code: 0 (ok)
|
||||
```
|
||||
|
||||
**중간 인증서가 함께 제공된다.**
|
||||
|
||||
### 개념 — `fullchain.pem` vs `cert.pem`
|
||||
|
||||
certbot 은 두 파일을 만든다.
|
||||
|
||||
| 파일 | 내용 | nginx 에 넣으면 |
|
||||
|---|---|---|
|
||||
| `cert.pem` | **리프만** | **일부 클라이언트에서 검증 실패** |
|
||||
| **`fullchain.pem`** | 리프 + 중간 | 정상 |
|
||||
|
||||
**브라우저는 중간 인증서를 캐시하고 있어 `cert.pem` 으로도 대개 동작한다.**
|
||||
그래서 실수해도 개발 중에는 안 드러나고, **캐시가 없는 클라이언트
|
||||
(모바일 앱, curl, 다른 서버)에서만 깨진다.**
|
||||
|
||||
```bash
|
||||
openssl s_client -connect <host>:443 -servername <host> | grep -E "^ *[0-9] s:"
|
||||
```
|
||||
|
||||
**단계가 2개 이상이면 fullchain 이고, 1개면 cert.pem 을 쓴 것이다.**
|
||||
이 실험대는 4단계로 정상이다.
|
||||
|
||||
---
|
||||
|
||||
## 3. 갱신 자동화는 동작한다
|
||||
|
||||
```
|
||||
NEXT LEFT LAST PASSED
|
||||
Fri 2026-09-04 17:03:46 KST 1h 54min Fri 2026-09-04 03:19:39 KST 11h ago
|
||||
타이머 enabled: enabled / active: active
|
||||
```
|
||||
|
||||
**하루 두 번 돌고, 11시간 전에 실제로 실행됐다.**
|
||||
|
||||
```
|
||||
만료: Dec 2 00:47:22 2026 GMT
|
||||
남은 일수: 88일
|
||||
```
|
||||
|
||||
**아직 갱신하지 않은 것이 정상이다** — Let's Encrypt 는 90일 발급이고
|
||||
certbot 은 **30일 남았을 때** 갱신한다. 지금 실행돼도 아무것도 안 한다.
|
||||
|
||||
> **타이머가 돌았다는 것과 갱신이 됐다는 것은 다르다.**
|
||||
> "타이머가 active 니까 괜찮다" 는 확인이 아니다. **실제 갱신은 58일 뒤**이며,
|
||||
> 그때 처음으로 절차가 시험된다.
|
||||
|
||||
---
|
||||
|
||||
## 4. ★ 못 한 것 — 강제 갱신과 무중단 확인
|
||||
|
||||
계획서의 D-4 는 이렇게 적혀 있었다.
|
||||
|
||||
```bash
|
||||
sudo certbot renew --force-renewal
|
||||
```
|
||||
|
||||
**실행할 수 없었다.**
|
||||
|
||||
```
|
||||
$ sudo -n -l
|
||||
sudo: a password is required
|
||||
$ sudo -n systemctl reload nginx
|
||||
sudo: a password is required
|
||||
```
|
||||
|
||||
**test-server 의 sudo 는 비밀번호를 요구한다.** 게스트(kc-lab-1/2)는 무암호라
|
||||
A층에서 `conntrack`·`tc` 를 자유롭게 썼는데, **호스트는 다르다.**
|
||||
|
||||
> **이 사실은 B-7 에서 처음 드러났다** — nginx 설정을 읽으려던 시도가 계속
|
||||
> 빈 결과였고, 그게 **sudo 의 조용한 실패**였다. 여기서 다시 확인된다.
|
||||
|
||||
### 그래서 답하지 못한 것
|
||||
|
||||
| 계획서의 항목 | 상태 |
|
||||
|---|---|
|
||||
| nginx reload 타이밍에 무중단인가 | **미측정** |
|
||||
| 갱신 중 진행 중이던 요청은 | **미측정** |
|
||||
| `certbot-renew.timer` 가 실제 갱신을 하는가 | **미측정** (58일 뒤에야 알 수 있다) |
|
||||
|
||||
### 이론적으로는 무엇을 기대하는가
|
||||
|
||||
```
|
||||
certbot renew → 새 인증서 파일 저장
|
||||
└─ deploy-hook: nginx -s reload
|
||||
└─ nginx 는 새 워커를 띄우고 옛 워커는 진행 중 요청을 끝낸 뒤 종료
|
||||
→ graceful. 진행 중 요청은 옛 인증서로 완결된다
|
||||
```
|
||||
|
||||
**nginx 의 reload 는 설계상 무중단**이지만, **확인하지 않았으므로 그렇게
|
||||
쓰면 안 된다.** 이 실험대에서 반복해 배운 것이 바로 그것이다 —
|
||||
A-1 의 NetworkPolicy, A-3 의 `--grace-period=0`, B-5 의 AOF 모두
|
||||
**"그럴 것이다" 가 틀렸던 사례**다.
|
||||
|
||||
---
|
||||
|
||||
## 5. 재현 절차 (명령어)
|
||||
|
||||
```bash
|
||||
# 1. 인증서 내용 — 밖에서 볼 수 있다
|
||||
echo | openssl s_client -connect auth.hyeonworks.com:443 -servername auth.hyeonworks.com 2>/dev/null \
|
||||
| openssl x509 -noout -subject -issuer -dates -ext subjectAltName
|
||||
|
||||
# 2. 체인 완결성 — 단계가 1개면 cert.pem 을 쓴 것이다
|
||||
echo | openssl s_client -connect auth.hyeonworks.com:443 -servername auth.hyeonworks.com 2>/dev/null \
|
||||
| grep -E "^ *[0-9] s:|Verify return code"
|
||||
|
||||
# 3. 갱신 자동화
|
||||
systemctl list-timers certbot-renew.timer --no-pager
|
||||
systemctl is-enabled certbot-renew.timer
|
||||
|
||||
# 4. 강제 갱신 (sudo 필요 — 이 실험대에서는 불가)
|
||||
sudo certbot renew --force-renewal
|
||||
# 갱신 중 다른 창에서:
|
||||
# while true; do curl -s -o /dev/null -w '%{http_code} ' https://auth.hyeonworks.com/realms/master; sleep 1; done
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 남긴 것
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **강제 갱신 + 무중단 측정** | sudo 권한이 필요하다 |
|
||||
| **SAN 확장** | 새 호스트를 쓰려면 재발급 — B-7 에서 실제로 걸렸다 |
|
||||
| **D-3 과 연결** | 인증서 **개인키**도 같은 비밀 관리 문제다 |
|
||||
Reference in New Issue
Block a user