LabHub
开始
学习 学习路径 课程

Envoy 内部结构

授权服务器宕机时,拦截还是放行?

在 LabHub 中继续学习

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

한 줄 요약

ext_authz 는 요청마다 프록시 밖의 인가 서버에 "이 요청을 들여보내도 되나" 를 묻는 HTTP 필터다. 묻는 방식은 두 가지(HTTP·gRPC)이고, 인가 서버가 답하지 못할 때 막을지 흘릴지는 설정 한 줄(failure_mode_allow)이 정한다.

왜 이게 필요했나

로컬 속도 제한이나 장애 주입은 Envoy 가 설정만 보고 판단할 수 있다. 인가는 그렇지 않다. "이 토큰의 주인이 이 주문을 볼 수 있는가" 는 사용자 DB·권한 표·조직 정책을 아는 쪽만 답할 수 있고, 그런 판단을 서비스마다 코드로 넣으면 서비스 수만큼 서로 다른 인가가 생긴다. 한 서비스만 규칙을 늦게 고쳐도 그 서비스가 구멍이 된다.

그래서 판단을 한곳(인가 서버)에 모으고, 프록시가 요청을 붙잡아 둔 채 그 서버에 묻는 구조가 생겼다. OPA 를 Envoy 옆에 붙이는 방식, 사내 인가 서비스를 두는 방식이 전부 이 필터를 쓴다. Istio 의 AuthorizationPolicy 에서 action: CUSTOMprovider 를 쓰면, istiod 는 메시 설정(extensionProvidersenvoyExtAuthzHttp·envoyExtAuthzGrpc)을 보고 사이드카에 바로 이 필터를 넣는다.

어떻게 동작하나

HTTP 방식 — Envoy 가 원래 요청의 메서드·경로로 인가 서버에 요청을 한 번 더 보낸다. 본문은 비우고(Content-Length: 0), 헤더는 골라서 싣는다. 기본으로 실리는 것은 Host·Method·Path·Content-Length·Authorization 뿐이고, 나머지는 allowed_headers 에 적어야 넘어간다. 인가 서버가 2xx 를 돌려주면 허용, 그 밖이면 그 상태 코드와 본문이 그대로 클라이언트에게 간다.

gRPC 방식 — 요청을 다시 보내지 않고, 요청의 속성(출발지·목적지·헤더·경로·TLS 정보)을 CheckRequest 라는 protobuf 로 넘긴다. 서비스 이름은 envoy.service.auth.v3.Authorization, 메서드는 Check 다. 이쪽은 allowed_headers 를 비워 두면 모든 헤더가 넘어간다. 같은 필터인데 두 방식의 기본값이 반대인 것은 옛 동작을 지키려는 호환성 때문이라고 문서가 밝힌다. 인가 서버를 바꾸면서 방식을 바꾸면, 정책이 기대던 헤더가 조용히 사라질 수 있다.

헤더는 양쪽으로 흐른다.

설정 언제 어디로
allowed_upstream_headers 허용 업스트림 요청에 붙인다(같은 이름은 덮어쓴다)
allowed_client_headers 거절 클라이언트 응답에 붙인다
gRPC OkHttpResponse.headers 허용 업스트림 요청에 붙인다

덮어쓴다는 성질이 중요하다. 인가 서버가 x-user: alice 를 정해 주면, 클라이언트가 같은 헤더에 admin 을 적어 보내도 업스트림에는 alice 가 닿는다. 업스트림이 그 헤더를 믿고 권한을 판단한다면, 이 성질이 위조를 막는 마지막 벽이다.

인가 서버가 답하지 못하면 — 연결 실패, 시간 초과, 5xx 가 여기 든다.

failure_mode_allow: false   status_on_error(기본 403)를 돌려준다   ← 막는다(fail closed)
failure_mode_allow: true    요청을 그대로 보낸다                    ← 흘린다(fail open)
  + failure_mode_allow_header_add: true   업스트림에 x-envoy-auth-failure-mode-allowed: true

통계는 http.<HCM stat_prefix>.ext_authz.<필터 stat_prefix>. 아래 ok·denied·error·failure_mode_allowed 로 쌓인다. 흘리는 쪽을 골랐다면 failure_mode_allowed 가 오르는 순간이 곧 인가 없이 요청이 지나간 순간이다.

경로마다 끌 수 있다. 라우트의 typed_per_filter_configExtAuthzPerRoute 를 넣고 disabled: true 로 두면 그 경로는 인가 서버에 아예 묻지 않는다. 건강 검사·정적 파일처럼 토큰이 없는 요청에 쓴다.

현장에서 만나는 모습

"인가 서버를 배포하는 동안 전 서비스가 403 이었습니다." 막는 쪽의 대가다. 인가 서버가 모든 요청의 경로 위에 있으므로 그 가용성이 곧 서비스의 가용성이 된다. 인가 서버를 프록시 옆에 사이드카로 붙이거나(OPA 의 흔한 배치), 여러 대로 두고 짧은 timeout 을 거는 이유다.

"보안 점검에서 인가가 꺼진 시간이 나왔습니다." 흘리는 쪽의 대가다. 인가 서버가 죽은 그 몇 분 동안 모든 요청이 통과했는데, 표시 헤더도 통계 알림도 없었다면 그 사실은 나중에 로그를 뒤져야 드러난다.

"gRPC 로 바꿨더니 모든 요청이 통과합니다." 인가 클러스터에 HTTP/2 를 켜지 않아 매번 연결이 실패하는데, 흘리는 쪽 설정이라 조용히 통과한 경우다. 통계의 error 가 요청 수만큼 오르고 있을 것이다.

공식 문서: External Authorization filter · ext_authz API · Authorization service API · Istio External Authorization

다음 실습에서 할 것

인가 서버를 HTTP 판과 gRPC 판으로 하나씩 직접 쓰고, 각각을 묻는 Envoy 두 대를 띄운다. 위조한 신원 헤더가 업스트림에서 어떻게 바뀌는지, 두 방식이 인가 서버에 넘기는 헤더가 어떻게 다른지를 서버 로그로 확인한다. 건강 검사 경로만 인가를 끄고, 마지막으로 두 인가 서버를 죽여 막는 쪽과 흘리는 쪽이 실제로 어떤 응답을 내는지 잰다.