CronJob JWKS 유실 — 이미지에 없는 도구와 set -e
JWKS(JSON Web Key Set)는 게이트웨이가 JWT 서명을 검증할 때 쓰는 공개키 묶음입니다. 보통 발급자의 OIDC 엔드포인트에서 주기적으로 받아 ConfigMap 에 저장해 두는데, 이 갱신이 조용히 망가지면 게이트웨이가 유효한 토큰까지 거부합니다.
게이트웨이에서 JWT 검증이 kid not found in jwks 로 실패하기 시작했습니다.
JWKS 를 담은 ConfigMap 을 열어보니 키가 1개뿐이었습니다. 발급자 쪽 OIDC
엔드포인트는 5개를 서빙하고 있었습니다. 누가 4개를 지운 게 아니라, 갱신
스크립트가 조용히 망가뜨린 거였습니다.
원인 — 베이스 이미지에 없는 python3
JWKS 를 주기적으로 받아 ConfigMap 을 갱신하는 CronJob 이 있었습니다. 스크립트가 받은 JSON 을 이스케이프하려고 중간에 python 을 끼웠습니다.
JWKS=$(curl -s "$OIDC_URL/keys" | python3 -c 'import sys,json; print(json.dumps(sys.stdin.read()))')
kubectl create configmap jwks --from-literal=jwks.json="$JWKS" ...
이미지는 bitnami/kubectl 이었습니다. 이 이미지엔 python3 이 없습니다.
(wget 도 없습니다.) 그러니 저 줄은 python3: command not found 로 실패하고,
JWKS 는 빈 값이 됩니다.
진짜 문제 — set -e 없어 멈추지 않는 스크립트
명령어가 없어서 실패한 것 자체보다, 스크립트가 거기서 안 멈춘 게 더
문제였습니다. set -e 가 없었습니다. 그래서 JWKS 가 비어도 다음 줄이 그냥
실행돼서, 빈(혹은 깨진) 값으로 ConfigMap 을 덮어썼습니다. CronJob 은
Completed 로 끝났습니다. 겉으론 매번 성공입니다.
실패가 에러로 드러나지 않고 정상 완료로 기록됩니다.
해결 — 있는 도구만 쓰고 크게 실패시키기
python 을 걷어내고, kubectl 이 이미 할 줄 아는 방식으로 바꿨습니다. 이스케이프도
--from-literal + --dry-run=client -o yaml | kubectl apply 로 해결됩니다.
set -e
JWKS=$(curl -sf "$OIDC_URL/keys")
kubectl create configmap jwks \
--from-literal=jwks.json="$JWKS" \
--dry-run=client -o yaml | kubectl apply -f -
바뀐 건 세 가지로, python3 을 걷어내 이미지에 있는 것만 쓰게 하고, wget 을
curl 로 바꾸고, set -e 와 curl -f 로 실패하면 즉시 멈추게 했습니다.
이제 갱신이 실패하면 CronJob 이 실패로 뜨고, 옛 ConfigMap 은 그대로 남습니다.
조용한 실패가 나오는 두 지점
kubectl 이미지에는 kubectl 만 있습니다. python 도 wget 도 없어서, 당연히
있으려니 하고 쓴 도구는 그 줄만 조용히 실패합니다.
set -e 없이 짠 파이프라인은 중간이 깨져도 끝까지 달려서 멀쩡한 데이터를 빈
값으로 덮습니다. 갱신 작업은 실패했을 때 기존 값을 안 건드리는 쪽이 성공보다
중요합니다.
댓글 0