swagger 보안 설정 + Istio VirtualService
@nestjs/swagger는 Nest.js 서버에서 REST API를 문서화할 수 있는 패키지입니다.
문서에 별도 보안 설정을 하지 않은 상태에서 URL이 노출될 경우 문서 열람 및 API 실행이 가능한 위험성이 있습니다.
환경 제한
swagger 문서를 초기화하는 코드를 APP_ENV 조건문으로 감싸 로컬/개발 환경 등 특정한 조건하에서만 생성할 수 있습니다.
id, password 설정
설정된 id, password를 입력해야 접근 가능하도록 설정할 수 있습니다. (참고)
서비스에 이미 개발되어 있는 인증/인가와는 별개로 앱 초기화시 원하는 엔드포인트에 지정하는 방식입니다.
Bearer 인증 적용
인증 token을 입력하도록 설정할 수 있습니다. (참고)
IP Whitelist 설정
허용 ip 외엔 에러를 뱉는 Middleware를 만들고, 이를 swagger 문서 경로에서 사용하게끔 설정합니다.
접근을 허용할 ip 목록은 환경변수로 설정해줍니다.
회사에선 대개 GraphQL을 사용하고 있기 때문에 swagger를 생각할 일이 별로 없는데요,
최근엔 비개발자 동료가 저를 거치지 않고 몇몇 api를 사용할 수 있게끔 제공하려다보니 고려하게 되었습니다.
특정 상위 권한의 사용자가 써야 하는 기능인데 해당 권한에 대한 정책을 정하고 트래픽 핸들링하고 전용 페이지를 만드는 일이 계속 밀리다 보니...
결론적으로 이 임시방편으로는 IP Whitelist를 설정했습니다.
- 환경 제한 -> 운영 환경에서 쓰면서 외부 접근을 막으려는 목적이라 기각.
- is, password 설정 -> 물리적 보안이 취약하여 퇴사자 발생시 관리 필요.
- Bearer 인증 적용 -> 비개발자에게 사용성이 안 좋음. 특정 사용자만 접근 가능하게 하려면 지저분해짐.
- 번외) swagger 문서의 path를 복잡하게 만들기 -> id, password를 하드코딩하는 것과 다를바 없음.
- IP Whitelist 설정 -> 사내 ip에 이미 적용된 보안 정책(VPN 등)에 묻어갈 수 있고 동료 입장에선 URL만 알면 됨.
Istio VirtualService
swagger 문서 설정 후 로컬에서는 http://localhost:3000/docs 이렇게 접근할 수 있는데,
그 외의 환경에서는 http://도메인주소/abc/docs 이런 식으로 끼어들어간 게(이하 /abc) 있어야 접근할 수 있었습니다.
main.ts에서 문서 초기화(SwaggerModule.setup)시 설정하는 path에 /abc를 추가해준 뒤에도 swagger에서 api 실행시엔 /abc가 없는 곳으로 요청이 가서 404가 떴습니다.
로컬에서 서버 띄우면 /abc가 없고, pod로 떠있는 개발 및 운영 환경에서는 /abc가 들어가있고, 프론트에서 해당 서버로 요청을 보낼 때 url에는 /abc가 존재하니 인프라 설정 어딘가에서 /abc를 떼고 해당 서버로 보내주는 상태로 추정되었습니다.
swagger 설정 이전, GraphQL 쿼리칠 때도 환경별로 /abc 유무가 다르단걸 알긴 했지만 이번엔 좀 더 번잡스러우니 고쳤어요.
VirtualService에서 /abc가 붙어 있는 요청은 해당 서버로 갈 요청이라고 판단하고 /abc를 뗀 뒤에 (rewrite) 해당 서버로 보내주고 있었습니다.
프론트 -> VirtualService (여기서 /abc 제거) -> 서버
프론트에서 갖고 있는 해당 서버의 url 값 말미에 /abc가 포함되어 있었고, swagger 문서는 프론트와 무관하게 서버의 주관이기 때문에 /abc 없이는 원하는 서버로 라우팅되지 못한거죠.
서버의 globalPrefix로 /abc를 설정해주고 VirtualService의 rewrite 부분을 제거하면 해결입니다.
만일 당장 VirtualService 설정을 건들 수 없다면 swagger에서 api 실행시 /abc로 요청을 보내게끔 초기화할 수 있습니다.
(전략)
const config = new DocumentBuilder()
.setTitle('My API')
.setDescription('API description')
.setVersion('1.0')
.addServer('/abc') <- 여기!
.build();
(중략)
SwaggerModule.setup('abc/docs', app, document);
(후략)