allocProc
7 min readApr 17, 2022

안전한 API 디자인하기

이번 글은 일상 속 사물이 알려주는 웹 API 디자인-Arnaud Lauret 의 chapter 8 을 정리한 내용입니다.

api 를 설계함에 있어서 보안은 아무리 신경써도 모자라지만 최소한 이것만큼은 꼭 고려해서 디자인하자!

1. 접근 제어

2. 민감한 정보 처리

접근 제어

  • API 를 안전하게 만들기 위해서는 허용된 consumer 만 API 를 사용하도록 해야 한다.
  • 같은 API 라고 한들, 요청한 사용자가 누구냐에 따라 그 결과는 달라야한다.

최소 권한의 원칙

  • 컨슈머가 접근 가능한 영역을 꼭 필요한 수준으로 제한을 둠으로써, 공격에 대한 가능성을 줄일 수 있다.

Scope 란?

  • API 로 달성하고자 하는 하나의 목표 단위

최소 권한의 원칙에 따라 최대한 적절한 스코프를 선택하는 것이 중요하다.

다만 스코프 하나는 너무 좁지도, 너무 넓지도 않아야한다.

스코프가 하나의 카테고리처럼 너무 넓으면 과도한 권한 부여로 이어질 것이고, 스코프 하나의 권한이 작아서 많은 수의 스코프가 탄생한다면 복잡도가 증가한다.

사용자가 API 를 이용해 달성할 수 있는것을 기반으로 나눠보자.

가령 센디로 비유하자면, 오더를 수락하기 정도로 스코프를 만들어보면 해당 스코프에는 오더 조회하기, 오더 상세보기, 오더 수락하기 등 3가지의 동작에 대한 권한이 포함될 것이다. (그림 8.11 참조)

스코프를 정의하는 전략에는 불행하게도 정답이 없다. 😨 API 컨슈머들과 API 개발자들, 그리고 최종 사용자에 따라서 가장 적합한 방법이 달라질 수 있다. 전략은 항상 유연하게 택하자. 꼭 하나만 고수할 필요는 전혀 없다.

유연하게 스코프를 정의하다 보면 스코프 간에는 일부 겹치는 영역이 생긴다. 이는 지극히 정상적이다.

다른 결과를 제공하는 두 개의 동일한 요청

계좌 목록을 제공한다는 동작은 일반 고객과 관리자의 경우가 다르게 동작해야한다. 하지만 REST API 의 형태인 GET /account 형태에 파라미터가 붙거나 하는 변형은 없다. 이 동작은 오로지 최종 사용자가 누구냐에 따라 달라졌다. 어떻게 이것을 가능하게 할까?

최종 사용자를 구분하기 위한 파라미터는 사실 존재한다. 단지 보이지 않게 숨겨져 있을 뿐이다.

Access Token 을 리퀘스트에 헤더로 포함하여 API 를 호출하기 때문에 사용자를 구분짓는 것이 가능하다. API 서버의 구현체가 이런 토큰이 포함된 리퀘스트를 받게 될 경우, 보안 데이터와 필요한 모든 접근 제어를 즉시 제공한다.

스코프를 고려하며 작업을 하다 보면 API 디자니어가 디자인 과정 중 프로바이더 관점을 배제하고 디자인을 하고 있음에도, 실제 구현에서 무슨 일이 일어나는지 알고 이를 고려해야하는 경우가 있다.

민감한 요소의 취급

데이터가 안정한 연결을 통해서 전달되었다 한들, 연관된 모든 데이터를 반환하는 것이 현명할까? 정보를 반환하기 전에 민감한 정보는 아닐지 다시 한 번 생각을 해볼 필요가 있다.

민감한 데이터는 최대한 노출시키지 않아야 한다. 그럼에도 노출시켜야 하는 상황이 온다면,

  • 어떤 데이터가 민감한 데이터인지 그 항목을 식별하고
  • 그 중 일부라도 제거하고 그 외의 것만 표현함으로써 의미 있고 적절한 수준으로 안전하게 데이터를 표현할 수 있다.

물론 ‘민감하다’ 는 정도는 도메인이나 업계에 따라 다르게 받아들일 수 있다.

순차적인 DB ID 를 반환하는 것은 치명적인 데이터에 대한 힌트를 제공하는 것과 마찬가지다.

적절한 표현을 선택하기

우리가 취급하는 데이터가 민감 정보라고 판명이 났다면, 그 다음에 행해야 할 행동은 이 데이터에 적절한 표현을 선택하는 것이다.

  • 필수요소가 아니라면 제거하기
  • 제거할 수 없다면, 덜 민감한 형태로 표현을 순화하기
    - 일부를 생략하거나 변형하기
    - 민감 데이터가 식별자의 역할을 한다면, 값의 대치를 통해 의미없는 새로운 식별자로 만들기
  • 민감 정보가 정말로 필요하다면, 정보 자체를 암호화 하기
    - 컨슈머와 프로바이더 사이 메시지의 전체 내용을 암호화, 복호화 하기

API 의 목표가 무엇이건간에, 민감한 데이터를 포함하고 있다면 민감한 요소로 취급되어야 한다.

위 같이 표현을 바꿔서 정보를 덜 민감하게 만드는 방식이 항상 가능한건 아니다. 몹시 중요한 이유로 민감한 정보를 컨슈머나 최종 사용자에게 제공해야한 하는 상황이 있다.

이런 상황에서는 이 데이터에 접근할 수 있는 권한을 확실하게 통제해야한다.

하지만 내 개인적인 생각으로는 위와 같은 권한 제어나 스코프 제어는 당연히 이루어 져야하고, 민감한 표현의 순화는 가능한한 꼭 이루어지는게 맞지 않을까? 둘 중 하나를 꼭 선택해야 하는 건 아닐거라 생각한다.

에러 피드백 : 허용되지 않는 행위를 시도한다면?

  • 토큰을 누락시키거나 유효하지 않을 경우 : 401 UNAUTHORIZED 리스폰스가 알맞다.
  • 토큰은 유효하나 권한이 없을 경우 : 403 FORBIDDEN 리스폰스가 알맞다.
  • 리스폰스와 더불어 어떤 문제로 허용이 되지 않았는지 설명하는 적절한 메시지는 필수이다.

하지만 때때로 에러 메시지는 정보 유출로 판단될 수 있다.

한 사용자가 어떤 카드로 결제를 시도했다고 하자, 만약 해당 사용자가 카드의 주인이 아니라면 403 FORBIDDEN 리스폰스와 함께 ‘사용자가 해당 카드에 접근 권한이 없습니다’ 와 같은 메시지가 출력될 것이다. 하지만 이는 해당 카드가 실제로 존재한다는 것을 알려준다.

이러한 정보 유출을 막으려면 API 는 사용자에게 해당 카드가 존재하지 않는다 정도로만 알려주는 것이 좋다. 그러면 403 보다는 404 NOT FOUND 리스폰스가 더 나을 수 있겠다.

그리고 에러에서 정보가 유출될 수 있는 또 하나의 치명적인 상황이 있다. 500 번대 에러이다.

500 번대 에러의 경우 대부분 서버 애플리케이션의 오류로 발생하는데, 메시지를 가공하지 않고 그대로 내보낸다면 서버가 실제로 어떻게 동작하고 있는지에 대한 실마리를 제공할 수 있다. 이는 아주 치명적이다. 그래서 메시지를 내보내더라도 stack trace 나 오류에 대한 너무 상세한 설명, 서버 주소, 혹은 에러의 시발점은 표현하지 않도록 하자.

아키텍쳐와 프로토콜을 얼마나 신뢰할 수 있을까?

위 그림은 은행 API의 기본적인 API 아키텍쳐이다. 위 그림에서 프로바이더와 컨슈머의 암호화된 연결은 생각한 것처럼 안전하지는 않다.

컨슈머는 HTTPS 와 프록시를 통해 인터넷에 접속하고, API 앞단에는 로드 밸런서 프록시가 존재한다. 이는 여러 리퀘스트를 여러 API 서버 인스턴스로 분산시켜준다. 모든 노드간의 상호작용은 TLS 를 통해 암호화된다.

안전해보이는가? 실은 그렇지 않다.

로드 밸런서는 일부 데이터(HTTP 메소드, URL, query param, HTTP 상태 코드, response time) 를 로그로 남긴다. 이는 누구든 로드 밸런서의 로그나 모니터링 툴에 접근할 수 있는 사람들은 위 정보들을 볼 수 있다는 것을 의미한다.

path variable 이나 query string 은 로그로 남을 수 있으므로 민감 정보는 포함하지 않도록 항상 주의하자.

  • API 는 정말 필요한 것들만 요청하고, 정말 보여줘야 하는 것들만 노출해야 한다.
  • 컨슈머들은 그들이 정말로 필요로 하는 것들만 접근할 수 있어야 한다.
  • 민감한 데이터와 목표의 범위는 매우 넓다.
  • 안전한 API 디자인을 위해 항상 주의를 기울이자.

No responses yet