Istio를 통한 header기반 API 라우팅/호출 시 cors preflight request 이슈 트러블슈팅 기록

현재 개발하고 있는 일부 컨테이너 기반의 서비스들을 Istio를 통해 서비스들을 구성하고 트래픽을 관리하고 있다.

이때 컨테이너 서비스가 같은 규격이 여러개가 같은 url과 port를 할당 받아서 사용해야는 애로 사항이 있어 Istio에서 header 기반으로 특별한 헤더가 있는 경우에만 라우팅이 될 수 있도록 구성하고 테스트를 진행했었다.

Istio Request Routing 예제와 같이 headermodel-api-key 값에 따라 정확하게 매치된 요청만 지정한 서비스로 라우팅 되도록 설정하였다.

apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  generation: 1
  labels:
    cheetah.xxx: xxx
  name: ...
  namespace: cheetah
spec:
  gateways:
  - model-deployment-gateway
  hosts:
  - '*'
  http:
  - match:
    - headers:
        model-api-key:
          exact: "xyz"
    route:
    - destination:
        host: model-deploy-01j46dj4ah5abd1zz8pc21vkg9
        port:
          number: 8000

curl 이나 기타 다른 도구들로 테스트 시에는 큰 문제가 되진 않았는데, 브라우저에서 해당 API를 호출해서 뭔가 메타데이터를 얻어오는 경우가 있었는데, 당연하게도 서비스되는 도메인이 같지 않고 IP 기반으로 서비스를 하다보니 CORS(교차 출처 리소스 공유) 이슈가 발생했었다.

브라우저에서는 CORS를 검증하기 위한 pre-flight(사전 검증 요청) 요청을 특별한 헤더(Options)와 함께 던져서 유효한 요청인지 검증 받고 실제 API를 요청하는데 이 과정에서 아래와 같이 오류가 발생했다.

Access to XMLHttpRequest at ‘https://myapi.com/Form’ from origin ‘https://myui.net’ has been blocked by CORS policy: Response to preflight request doesn’t pass access control check: Redirect is not allowed for a preflight request.

CORS Preflight 와 관련된 절차와 자세한 내용은 아래의 문서들이 진짜 정리(진리의MDN)가 잘되어 있다. 한번 훑어 보시길 권장한다.

Fetch Standard
Cross-Origin Resource Sharing (CORS) - HTTP | MDN
Cross-Origin Resource Sharing (CORS) is an HTTP-header based mechanism that allows a server to indicate any origins (domain, scheme, or port) other than its own from which a browser should permit loading resources. CORS also relies on a mechanism by which browsers make a “preflight” request to the server hosting the cross-origin resource, in order to check that the server will permit the actual request. In that preflight, the browser sends headers that indicate the HTTP method and headers that will be used in the actual request.

어찌됐든 일반적인 CORS 관련된 설정이거니 해서 공식 Istio 문서에서 Cors Policy 를 보고 아래와 같이 수정해서 테스트를 다시 진행했다.

apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  generation: 1
  labels:
    cheetah.xxx: xxx
  name: ...
  namespace: cheetah
spec:
  gateways:
  - model-deployment-gateway
  hosts:
  - '*'
  http:
  - corsPolicy:
      allowCredentials: true
      allowHeaders:
      - authorization
      - content-type
      - accept
      - origin
      - user-agent
      - model-api-key
      allowMethods:
      - POST
      - GET
      - PUT
      - DELETE
      - OPTIONS
      allowOrigins:
      - exact: '*'
      maxAge: 24h
    match:
    - headers:
        model-api-key:
          exact: "xyz"
    route:
    - destination:
        host: model-deploy-01j46dj4ah5abd1zz8pc21vkg9
        port:
          number: 8000

corsPolicyallowHeaderallowMethds에 허용할 값들을 위처럼 잘 설정하고 다시 테스트 해보는데 이번엔 뜬금없이 404 not found가 나고 있다.

다시 한번 절차데로 제대로 되고 있는지 이것저것 로그를 확인해 보았다.

MDN Pre-flight request

위 그림 처럼 가장 첫번째 Preflight 요청을 날리는 시점이 문제였다. 해당 부분은 axiosfetch 같은 라이브러리를 사용하여 날리는 요청이 아니라 브라우저 에서 CORS 확인을 위해 준비되는 절차인데, 특별한 헤더를 사용한 요청 즉 model-api-key라는 헤더를 통해 istio로 라우팅 한 경우가 문제가 되고 있었다.

브라우저에서 Options 메소드로 Preflight 검증 요청 시에는 custom header를 지원하지 않는 것이었다.

당연히 커스텀 헤더가 없어서 라우팅할 서버가 없기 때문에 404 오류가 발생되었다.

Istio의 설정으로 뭔가 가능하지 않을까 싶었지만 결국엔 방법을 찾진 못했고 결국엔 같은 url와 port로 떠 있는 preflight 검증용 dummy 서버를 만들어서 띄워두고 preflight 검증 시에는 dummy 서버로 요청하고 실 요청은 헤더를 기반으로 요청하게끔 기능은 변경 하였다.

정리

  • CORS 처리 절차에 대해서 숙지 필요
  • Istio와 상관 없이 브라우저에서 Preflight 요청에서는 사용자 정의 header가 포함되진 않는다.
  • Istio가 아니더라도 custom header를 반드시 사용해야 하는 경우라면 dummy 서비스를 하나 띄워두고 preflight와 본 요청을 분리하여 사용한다.