본문으로 건너뛰기
엔지니어링보안API
비수탁형 거래 설계: API 키 권한 범위와 출금에 절대 손대지 않기

비수탁형 거래 설계: API 키 권한 범위와 출금에 절대 손대지 않기

'비수탁형'은 보통 마케팅 용어로 쓰입니다. 정확하게 쓰면 이는 아키텍처적 제약이며, 이런 종류의 제약이 흥미로운 이유는 일을 더하는 게 아니라 없애기 때문입니다.

트레이딩 봇은 사용자를 대신해 주문을 발주해야 합니다. 거래소는 API 키로 이를 가능하게 하며, API 키에는 권한 범위(scope)가 딸려 있습니다. 읽기, 거래, 그리고 대부분의 거래소에서는 출금입니다. 따라서 설계상의 질문은 권한을 요청할지 말지가 아닙니다. 어떤 권한을 요청할지, 그리고 요청한 것보다 많은 권한을 받았을 때 무엇을 할지입니다.

핵심 요약

  • 권한을 사용하지 않겠다는 약속은 애초에 그 권한이 없는 것보다 약합니다. Freya는 출금 권한이 있는 키를 받아 놓고 무시하는 대신 아예 거부합니다.
  • 거부는 신뢰보다 강한 보장입니다. 우리 코드가 정확한지, 우리 팀이 정직한지에 기대지 않기 때문입니다.
  • 연결 절차가 사용자 계정에 무언가를 기록한다면, 그 기록은 수락 결정 이전이 아니라 이후에 이뤄져야 합니다. 그렇지 않으면 거절을 통보하러 가는 길에 상대방의 설정을 바꿔 버리게 됩니다.
  • 자금을 보관하지 않으면 어려운 문제 한 부류 전체가 사라집니다. 수탁 원장도, 대사(reconciliation)도, 출금 대기열도 없습니다.
  • 그 자리를 대신하는 것은 더 좁고 더 날카롭습니다. 시스템은 아무것도 되돌릴 수 없기 때문에 실시간으로 정확해야 합니다.

1. 권한을 거부하는 것이 사용하지 않겠다는 약속보다 낫다

뻔한 구현 방식은 사용자에게 읽기와 거래 권한이 있는 키를 요청하고, 출금 엔드포인트를 그냥 절대 호출하지 않는 것입니다. 업계 대부분이 이렇게 하며, 이것이 비합리적인 방식은 아닙니다.

Freya는 더 엄격하게 합니다. 출금 권한이 딸린 키가 들어오면 거부합니다. 사용자는 정확히 무엇을 해야 하는지 알려 주는 메시지를 보게 됩니다.

이 API 키에는 출금 권한이 켜져 있습니다. Freya는 절대 자금을 출금하지 않으므로 해당 권한이 있는 키는 받지 않습니다. 읽기와 거래 권한만 있는 키를 만들어 주세요.

같은 규칙이 Freya가 지원하는 세 거래소 모두에 적용됩니다. 우회 처리도, 관리자 예외도 없습니다.

처음에는 이것이 괜히 만드는 불편처럼 보입니다. 그 키는 작동할 것입니다. 사용자는 그 키를 만드느라 수고를 들였습니다. 키를 거부한다는 것은 사용자를 거래소로 되돌려 보내 다른 키를 하나 더 만들게 한다는 뜻입니다.

이유는 두 설계가 서로 다른 약속을 하기 때문이며, 그 차이는 최악의 날에만 드러납니다.

  • "우리는 출금 엔드포인트를 절대 호출하지 않는다"는 우리 행동에 관한 주장입니다. 우리 코드가 정확하고, 의존성이 깨끗하며, 배포 권한을 가진 모든 사람이 정직하게 남아 있는 한 성립합니다. 이는 기대할 만한 합리적인 것들입니다. 하지만 사용자가 검증할 수 있는 것은 아닙니다.
  • "그 키는 출금할 수 없다"는 키에 관한 주장입니다. 우리 코드가 무엇을 하든 상관없이 성립하는데, 거래소가 이를 강제하고 사용자가 자기 거래소 설정에서 직접 확인할 수 있기 때문입니다.

두 번째는 더 약한 요청이면서 더 강한 보장입니다. 안전 속성을 당신이 아닌 다른 주체가 강제하는 설계를 선호하세요. 특히 사용자가 당신 쪽을 감사할 방법이 없을 때 그렇습니다.

실무에서 중요한 부수적 이점이 하나 있습니다. 실패를 요란하게 만든다는 점입니다. 권한이 과한 키는 연결 시점에, 지금 막 설정을 진행 중이라 1분이면 고칠 수 있는 사용자 앞에서 걸립니다. 다른 방식이라면 권한이 과한 키가 멀쩡히 작동한 채로 남아 있고, 문제가 될 때까지 아무도 아무것도 알아채지 못합니다.

2. 무언가에 손대기 전에 거절하라

이것은 다른 엔지니어가 가장 가져갔으면 하는 세부 사항입니다. 잘못하기 쉽고, 잘못했을 때 거의 눈에 띄지 않기 때문입니다.

거래소를 연결하는 것은 순수한 읽기 작업이 아닙니다. 거래소에 따라, 계정을 사용 가능한 상태로 만들려면 계정에 기록해야 할 수도 있습니다. 계정 모드를 조정하거나, 포지션 모드를 설정하는 것 등입니다. 이는 설정 과정의 일부로 일어나는 정당한 단계들입니다.

이제 순서를 생각해 봅시다. 권한 확인이 그 기록 이후에 일어난다면, 거부된 키도 흔적을 남깁니다. 사용자는 자격 증명을 건넸고, 당신은 그들의 계정에서 설정을 바꿨으며, 그런 다음 거절을 통보했습니다. 사용자 입장에서는 자기 거래소 구성이 왜 두고 온 상태와 달라졌는지 설명할 길이 없습니다.

그래서 Freya가 정한 규칙은 이렇습니다. 거부된 자격 증명은 사용자 계정을 처음 발견한 상태 그대로 남겨 두어야 한다. 기록하는 단계는 무엇이든 수락 결정 이후에 와야 하며, 절대 그 이전이어서는 안 됩니다. 거절은 부작용 없이 도달할 수 있어야 합니다.

일반화하면 이는 남의 시스템에 적용한 트랜잭션적 사고에 불과합니다. 제3자 계정에 대한 기록은 롤백할 수 없으므로, 순서가 유일한 방어책입니다. 되돌릴 수 없다면 순서를 신중하게 정하세요.

버그 한 부류 전체에서 우리를 구해 준 구현 노트가 하나 있습니다. 거부는 특별한 플래그가 아니라 유효하지 않은 결과(invalid result)로 표현되므로, 검증 결과를 소비하는 모든 경로가 그 존재를 알 필요 없이 이를 처리합니다. Freya에는 연결 절차가 두 개 있는데, 둘 다 이 거부를 따로 물어볼 필요가 없었기 때문에 똑같이 물려받습니다. 규칙을 모든 소비자가 이미 읽는 타입에 인코딩하는 것이, 문서로 적어 두고 지켜지기를 바라는 것보다 낫습니다.

3. 이 제약이 없애는 것

바로 여기서 이 맞바꿈이 확실하게 유리해집니다.

자금은 사용자의 거래소 계정을 절대 떠나지 않습니다. 봇은 읽기 및 거래 권한 키로 거래하며, 잔액은 시작한 자리에 그대로 있습니다. 여기서 따라 나오는 것은, 우리 시스템에 아예 존재하지 않는 것들의 긴 목록입니다.

  • 수탁 원장이 없으므로, 내부 잔액과 외부 잔액 사이의 대사도 없습니다
  • 출금 대기열도, 승인 워크플로도, 거래 자본에 대한 일일 한도도 없습니다
  • 그 자본을 위한 핫월렛/콜드월렛 분리도, 그것을 둘러싼 키 관리도 없습니다
  • 회사가 사라지면 사용자 자금이 어떻게 되는가 하는 문제도 없습니다

이 하나하나가 수탁형 제품에서는 각각 하나의 완전한 서브시스템이며, 각각이 돈이 사라질 수 있는 지점입니다. 여기에서 이것들이 없는 이유는 우리가 그것들을 해결해서가 아니라, 그것들을 만들어 내는 대상 자체를 애초에 떠안지 않았기 때문입니다.

다만 뭉뚱그리기보다 정확하게 짚고 싶습니다. 이는 거래 자본에 해당하는 이야기입니다. 구독에 쓰이는 플랫폼 잔액은 별개의 사안이며 자체적인 처리 방식이 있습니다. 비수탁형 속성은 거래에 쓰이는 돈에 관한 것이고, 그것이 가장 중요한 돈입니다.

4. 이 제약이 고쳐 주지 않는 것

문제를 없애는 제약은 실수를 바로잡을 때 쓸 도구도 함께 없애며, 이 점은 솔직하게 말할 가치가 있습니다.

거래는 최종적입니다. 수탁 방식이라면 내부 이체는 원칙적으로 되돌릴 수 있습니다. 남의 거래소 계정에서 체결된 주문은 되돌릴 수 없습니다. 모든 엔지니어링 무게는, 요청이 나간 뒤에 대사하는 것이 아니라 요청이 떠나기 전에 정확해지는 쪽으로 옮겨 갑니다. 이는 들리는 것보다 어려운 규율이며, 우리 작업의 상당 부분이 검증, 정밀도, 멱등성에 관한 것인 이유입니다.

거래 권한도 엄연한 권한입니다. 유출된 읽기 및 거래 키는 계정을 외부 주소로 비워 낼 수 없습니다. 이것이 사람들이 실제로 두려워하는 결과입니다. 하지만 주문은 발주할 수 있습니다. 피해 범위는 0이 아니라 한정된 것이며, 이를 0이라고 표현한다면 정직하지 못한 일입니다.

사용자에게도 여전히 의무가 있습니다. 키 교체, 여러 서비스에 걸쳐 키를 재사용하지 않기, 같은 계정에 또 무엇이 접근 권한을 가졌는지 지켜보기는 사용자의 몫으로 남습니다. 우리 아키텍처는 피해를 한정할 뿐, 키를 무해하게 만들지는 않습니다.

다른 팀에게 하고 싶은 말

  • 가장 좁은 권한 범위를 요청한 뒤, 실제로 그렇게 받았는지 검증하세요. 더 적게 요청하는 것과 더 적게 받는 것은 같지 않습니다.
  • 당신의 코드 바깥에서 강제되는 보장을 선호하세요. 특히 사용자가 당신을 신뢰하지 않고도 확인할 수 있는 보장을요.
  • 거절을 부작용 없게 만드세요. 절차가 제3자 계정에 기록한다면, 기록하는 모든 것은 수락 결정 이후에 와야 합니다.
  • 규칙을 소비자가 이미 들여다보는 곳에 인코딩하세요. 공유 타입으로 표현된 규칙은 모든 호출자가 기억할 필요가 없습니다.

더 큰 요점은, "우리는 X를 하지 않는다"와 "우리는 X를 할 수 없다"가 랜딩 페이지에서는 비슷하게 읽히지만 사고 상황에서는 완전히 다르다는 것입니다. 약속을 앞의 범주에서 뒤의 범주로 옮길 수 있을 때마다 비용은 대개 작고, 그 대가는 다른 모든 것이 잘못 돌아가는 바로 그 순간에 드러납니다.


관련 읽을거리

배운 것을 실전에 적용하세요.

오늘 Freya 계정을 만들어 배운 지식을 실전에 적용해 보세요.

무료로 시작
이 콘텐츠는 교육 목적으로만 제공되며 금융 자문을 구성하지 않습니다. 암호화폐 거래에는 상당한 리스크가 있습니다. 잃을 수 없는 금액 이상으로는 절대 투자하지 마세요.