고객사에서 받은 공인 인증서를 인그레스 TLS 시크릿에 넣었는데 브라우저에 여전히 경고가 뜨고, 인증서를 살펴보면 컨트롤러가 만든 기본 인증서(Kubernetes Ingress Controller Fake Certificate)가 내려온다. Apache 나 IIS 에서는 같은 파일로 문제없이 서비스되던 인증서다.
컨트롤러 로그에는 대체로 이렇게 남는다.
Unexpected error validating SSL certificate "<ns>/<secret>" for server "<fqdn>"
SSL certificate "<ns>/<secret>" does not contain a Common Name or Subject Alternative Name for server "<fqdn>"
Using default certificate
고객사가 준 파일 묶음에는 서버 인증서, 중간 CA, 루트 CA, 개인키가 뒤섞여 있는 경우가 많다. 이름만 보고 고르면 틀린다. 아래 순서로 본다.
openssl x509 -in server.crt -noout -text | grep -A1 'Extended Key Usage'
TLS Web Server Authentication 이 있어야 한다. 클라이언트 인증 용도만 들어 있으면 인그레스에 쓸 수 없다.
openssl x509 -in server.crt -noout -text | grep -A1 'Subject Alternative Name'
ingress-nginx 는 CN 이 아니라 SAN 을 본다. 와일드카드 *.example.com 은 한 단계만 덮으므로 example.com 자체나 a.b.example.com 은 포함되지 않는다. 반대로 example.com 만 들어 있는 인증서로 *.example.com 호스트를 서비스할 수도 없다 - 위 로그가 정확히 그 상황이다.
인그레스는 서버 인증서 하나만 보내면 안 되고 서버 인증서 + 중간 CA 를 이어 붙인 파일을 필요로 한다.
cat server.crt intermediate.crt > tls.crt
openssl crl2pkcs7 -nocrl -certfile tls.crt | openssl pkcs7 -print_certs -noout
인증서가 둘 이상 나와야 한다. 하나뿐이면 클라이언트가 체인을 이어 붙이지 못해 검증에 실패한다. 루트 CA 는 붙이지 않는 것이 원칙이다 - 클라이언트가 이미 신뢰 저장소에 갖고 있어야 하는 앵커다.
openssl rsa -in tls.key -check -noout
암호를 물어보면 그 키는 인그레스에서 쓸 수 없다. nginx 나 IIS 는 기동할 때 사람이 암호를 넣거나 설정에 적어 둘 수 있지만, 인그레스 컨트롤러는 무인 기동이라 암호를 입력할 방법이 없다. 컨트롤러는 키 로딩에 실패하고 조용히 기본 인증서로 넘어간다. 웹서버에서는 되는데 인그레스에서만 가짜 인증서가 나오는 경우 대부분 이것이다.
암호를 아는 경우에만 제거할 수 있다.
openssl rsa -in server.key -out server.key.nopass
암호를 모르면 방법이 없다. 고객사에 암호 없는 PEM 키를 다시 요청한다.
openssl x509 -noout -modulus -in tls.crt | openssl md5
openssl rsa -noout -modulus -in tls.key | openssl md5
두 해시가 같아야 한다.
이름은 발급 기관마다 제각각이므로 subject 와 issuer 로 가른다.
for f in *.crt; do echo "== $f"; openssl x509 -in "$f" -noout -subject -issuer; done
| 판별 | 정체 |
|---|---|
| subject 가 서비스 FQDN, issuer 가 다른 이름 | 서버 인증서. tls.crt 의 첫 번째 |
| subject 와 issuer 가 서로 다르고, subject 가 다른 인증서의 issuer 와 같음 | 중간 CA. tls.crt 의 두 번째 |
| subject 와 issuer 가 같음 | 루트 CA. 서버에는 넣지 않고 클라이언트에 설치 |
루트 CA 파일 안에 중간 CA 까지 함께 들어 있는 경우가 있다. 이때는 파일을 그대로 쓰지 말고 중간 CA 블록만 잘라 낸다.
kubectl -n <namespace> delete secret <ingress-tls-secret>
kubectl -n <namespace> create secret tls <ingress-tls-secret> --cert=tls.crt --key=server.key.nopass
ca.crt 키는 넣지 않는다. 인그레스 예제에 나오는 ca.crt 는 서버 체인용이 아니라 클라이언트 인증(mTLS)에 쓸 CA 다. 일반 HTTPS 만 쓰면서 여기에 루트 CA 를 넣으면 검증이 꼬인다.
컨트롤러는 시크릿 변경을 감시하므로 원칙적으로 재기동이 필요 없다. 다만 시크릿 이름을 유지한 채 내용만 바꾸면 갱신이 늦게 반영되는 경우가 있어, 확실히 하려면 컨트롤러를 롤링 재시작한다.
kubectl -n ingress-nginx rollout restart deploy ingress-nginx-controller
openssl s_client -connect <ip>:443 -servername <fqdn> -showcerts </dev/null
Certificate chain 에 인증서가 둘 이상 나오고 subject 가 실제 서버 인증서면 정상이다. verify error:num=21:unable to verify the first certificate 가 나오면 체인이 부족하거나 로컬에 루트 CA 가 없는 것이다.
PC 에 설치해야 하는 것은 루트 CA 하나뿐이다. 서버 인증서를 PC 의 신뢰할 수 있는 루트 인증 기관에 넣으면 오히려 검증이 꼬인다. 서버는 fullchain 을 내려보내고, 클라이언트는 루트 CA 만 신뢰한다 - 이것이 표준 구성이다.
인그레스 리소스의 spec.rules.host 나 TLS 시크릿만 바꾼 경우 컨트롤러는 변경을 감시해 nginx 를 reload 하므로 백엔드 애플리케이션을 재기동할 필요가 없다. 애플리케이션이 외부 URL 을 설정값으로 들고 있는 경우(SAS Viya 의 sas.security.external.url 같은 것)에만 해당 서비스의 재기동이 필요하다.