LabHub
开始
学习 学习路径 课程

会话与令牌 — 从浏览器到服务网格

有了 mTLS 为什么还要验证 JWT

在 LabHub 中继续学习

한국어 원문으로 표시합니다.

한 줄 요약

서비스 메시에서 "누가 요청했나" 에는 두 개의 답이 있다. 어느 워크로드가 연결을 맺었는지는 mTLS 인증서가, 어느 사람을 대신해 요청하는지는 JWT 가 증명한다. 사이드카 프록시는 둘 다 검증할 수 있지만 둘은 서로를 대신하지 못한다.

왜 이게 필요했나

앞 모듈까지 토큰 검증은 애플리케이션 코드의 몫이었다. 서비스가 스무 개면 스무 곳에 같은 검증 코드가 있고, 그중 하나가 aud 검사를 빠뜨리거나 라이브러리 업데이트가 한 박자 늦는다. 메시는 이 검증을 모든 서비스 앞에 선 프록시로 옮긴다. 검증 규칙이 한 곳의 설정이 되고, 틀린 토큰은 애플리케이션 코드에 닿기도 전에 끊긴다.

그런데 메시를 들이면 흔히 이런 착각이 생긴다. "서비스 사이에 이미 mTLS 가 걸려 있으니 인증은 끝났다." mTLS 가 증명하는 것은 연결을 맺은 워크로드다 — 주문 서비스에 붙은 것이 프런트엔드 파드라는 사실. 그 요청이 앨리스의 것인지 밥의 것인지는 인증서에 없다. Istio 보안 개념 문서가 인증을 peer authentication(서비스 대 서비스)과 request authentication(최종 사용자)으로 나눠 설명하는 이유다.

어떻게 동작하나

Envoy 의 jwt_authn 필터는 서명·발급자·청중을 검증한다. 공개 키는 JWK Set으로 받는데, 원격 URL 대신 파일이나 인라인 문자열(local_jwks)로 줄 수도 있다. 토큰 헤더의 kid 로 어느 키를 쓸지 고른다 — 키를 교체할 때 옛 키와 새 키를 한 JWKS 에 함께 두는 것이 이 덕분이다.

설정 레퍼런스에서 이 모듈이 쓰는 필드는 이렇다.

forward                 기본값 false. 검증에 성공하면 원래 토큰을 요청에서 지운다.
forward_payload_header  검증된 페이로드를 base64url(JSON) 으로 이 헤더에 실어 보낸다.
payload_in_metadata     헤더 대신 동적 메타데이터에 넣는다(RBAC 같은 다음 필터용).
rules                   경로별 요구. 처음 맞는 규칙이 이기고, requires 가 비면 검증하지 않는다.
allow_missing_or_failed 없든 틀리든 통과시킨다 — 잘못 쓰면 필터가 장식이 된다.

forward 가 기본으로 꺼진 이유가 중요하다. 상류는 이미 검증된 클레임만 받으면 되고, 원래 토큰을 받은 상류는 그 토큰을 다른 서비스에 다시 쓸 수 있다. 토큰이 흘러가는 거리가 짧을수록 새어 나갈 곳이 적다.

거절 코드도 구별된다. 토큰이 없거나 서명이 틀리거나 만료되면 401, 서명은 맞는데 aud 가 이 서비스가 아니면 403 이다. RFC 7519 는 처리자가 aud 에 자기가 없으면 그 JWT 를 거절해야 한다(MUST)고 적는다.

mTLS 쪽은 리스너의 TLS 설정이다. require_client_certificate 를 켜면 유효한 클라이언트 인증서 없는 연결을 거절한다. 검증된 인증서의 정보는 x-forwarded-client-cert(XFCC) 헤더로 상류에 간다. URI 키에는 인증서의 URI SAN, 메시에서는 spiffe://신뢰도메인/... 모양의 SPIFFE ID가 담긴다. HCM 의 forward_client_cert_details 는 기본이 SANITIZE 라 받은 XFCC 를 버리고, SANITIZE_SET 은 mTLS 연결일 때 받은 것을 버리고 자기가 검증한 값으로 새로 채운다.

현장에서 만나는 모습

가장 흔한 사고는 "헤더를 믿는" 상류다. 상류가 x-jwt-payload 를 신원으로 믿는데, 프록시가 검증을 건너뛰는 공개 경로로 클라이언트가 그 헤더를 직접 붙여 보내면 상류는 가짜 신원을 그대로 믿는다. 검증하는 경로에서는 Envoy 가 검증된 값으로 덮어쓰지만 공개 경로는 아무도 손대지 않는다. 그래서 공개 경로에서는 그 헤더를 명시적으로 지운다.

두 번째는 토큰 없는 요청이다. Istio 문서는 RequestAuthentication 만 두면 토큰 없는 요청이 기본으로 통과한다고 적고, 거절하려면 인가 규칙을 따로 두라고 한다. "토큰이 있으면 검사한다" 와 "토큰이 있어야 한다" 는 다른 설정이다. Envoy 로 직접 짜면 requires 를 둔 규칙이 그 차이다.

다음 실습에서 할 것

cryptography 로 RS256 키와 JWKS 를 만들고 토큰을 손서명한다. Envoy 에 jwt_authn 을 걸어 유효한 토큰만 상류에 닿고 없음·위조·만료는 401, 다른 청중은 403 으로 상류 앞에서 끊기는 것을 확인한다. 검증된 클레임을 헤더로 넘기고, 공개 경로에서 흉내 낸 헤더를 지운다. 마지막으로 mTLS 체인을 더해 워크로드 신원과 사용자 신원이 따로 도착하는 것을 본다.