[실전 보안 11] 코드에 열쇠를 박지 마라

지난 여러 편에서 나는 반복해서 말했다. 인증의 서명 열쇠도, 메일 발송 열쇠도, 외부 서비스 접근 열쇠도 코드에 박지 말고 따로 보관하라고. 이번 편은 그 따로가 대체 무엇이며 왜 그렇게 중요한지를 다룬다. 이 주제를 독립된 편으로 떼어 낸 데는 이유가 있다. 나 자신이 시크릿 관리를 소홀히 해 사이트가 여러 번 흔들린 뼈아픈 경험이 있기 때문이다. 그 사고에서 배운 교훈을 솔직히 풀어 보려 한다. 이론보다 실패담이 더 오래 남는다는 걸 나는 안다.


1. 시크릿이란 무엇인가

여기서 말하는 시크릿은 서비스가 동작하는 데 필요하지만 외부에 노출되면 안 되는 비밀 값들이다. 앞서 인증 편에서 다룬 증표에 서명하는 열쇠가 대표적이다. 이 열쇠가 새면 공격자가 마음대로 유효한 증표를 찍어 내 아무 계정이나 흉내 낼 수 있다. 메일 발송 서비스나 저장소, 외부 도구에 접근하는 열쇠도 마찬가지다. 이런 값들은 사이트의 문을 여는 마스터키와 같아서, 하나라도 새면 큰 사고로 이어진다.


이 값들의 공통점은 코드가 실행될 때는 필요하지만 코드 자체에 적혀서는 안 된다는 것이다. 기능 로직은 누가 봐도 되지만, 이 열쇠들은 오직 우리 서버만 알아야 한다. 문제는 개발할 때 편하다는 이유로 이 값을 코드 어딘가에 그냥 적어 두고 싶은 유혹이 강하다는 점이다. 나도 초기에 그랬다. 일단 돌아가게 만드느라 열쇠를 코드에 임시로 넣어 두고, 나중에 옮기자고 미뤄 두곤 했다.


그 미룸이 위험했다. 코드에 열쇠를 적어 두면, 그 코드를 관리하는 이력 저장소에 열쇠가 고스란히 남는다. 한번 이력에 박힌 값은 나중에 코드에서 지워도 과거 기록에 계속 남아 있어, 저장소에 접근할 수 있는 누구나, 혹은 저장소가 실수로 공개되면 세상 누구나 그 열쇠를 볼 수 있게 된다. 코드 공개는 흔한 실수이고, 그 순간 박혀 있던 모든 열쇠가 유출된다. 이것이 시크릿을 코드에 박으면 안 되는 첫 번째 이유다.


두 번째 이유는 분리의 원칙이다. 코드는 무엇을 하는지에 대한 것이고, 시크릿은 그것을 실제 환경에서 어떻게 여는지에 대한 것이다. 이 둘을 섞으면 환경이 바뀔 때마다 코드를 고쳐야 하고, 실수로 노출될 위험도 커진다. 코드와 비밀을 분리해 두면 같은 코드를 여러 환경에서 쓸 수 있고, 열쇠만 따로 안전하게 관리하면 된다. 관심사를 분리한다는 좋은 설계 원칙이 보안에서도 그대로 통했다.


2. 그럼 어디에 두는가

코드에 안 둔다면 대체 어디에 둘까. 답은 배포 환경이 제공하는 별도의 비밀 저장소였다. 내가 쓰는 환경은 시크릿을 코드와 완전히 분리된 안전한 공간에 등록해 두는 기능을 제공한다. 코드가 실행될 때 이 값을 환경으로부터 전달받아 쓰되, 코드 어디에도 그 값 자체는 적히지 않는다. 열쇠는 금고에 넣어 두고, 코드는 필요할 때 금고에서 꺼내 쓰는 그림이다.


이 비밀 저장소에 값을 등록하는 작업은 코드를 배포하는 것과 별개로, 안전한 통로를 통해 한 번 해 둔다. 한번 등록한 시크릿은 다시 읽어 볼 수 없게 되어 있는 경우가 많다. 오직 등록과 교체만 가능하고 조회는 막혀 있어, 등록 이후에는 운영자조차 그 값을 직접 볼 수 없다. 이게 오히려 안전하다. 아무도 못 보는 값은 아무도 훔칠 수 없기 때문이다.


개발 중에 로컬에서 시험할 때도 시크릿을 코드에 넣지 않았다. 대신 코드가 관리되는 이력 저장소에 절대 올라가지 않는 별도의 설정 파일에 값을 두고, 그 파일은 저장소가 무시하도록 설정했다. 이렇게 하면 개발 편의와 안전을 함께 챙길 수 있다. 핵심은 어떤 경우에도 비밀 값이 코드 이력에 흔적을 남기지 않게 하는 것이었다. 개발이든 운영이든 이 선만은 지켰다.


시크릿을 다룰 때 또 하나 신경 쓴 건 접근 권한의 최소화였다. 각 열쇠는 꼭 필요한 만큼의 권한만 갖게 했다. 예를 들어 어떤 외부 서비스의 열쇠가 있다면, 그 열쇠가 할 수 있는 일을 우리가 실제로 쓰는 기능으로만 좁혔다. 그러면 설령 그 열쇠가 새더라도 피해 범위가 제한된다. 열쇠 하나가 모든 걸 열 수 있게 두지 않는 것, 이것도 피해를 가두는 중요한 원칙이었다.


3. 내가 겪은 시크릿 사고

이론만 늘어놓으면 와닿지 않을 테니 내 실패를 이야기하겠다. 한번은 배포 설정을 잘못 건드려, 사이트가 엉뚱한 상태로 배포되면서 인증 서명 열쇠가 사라지는 사고가 났다. 열쇠가 없으니 서버는 증표를 검증하지 못했고, 멀쩡히 로그인해 있던 사용자들이 전부 인증에 실패하기 시작했다. 사이트의 로그인 기능이 통째로 마비된 것이다. 그날 나는 같은 실수를 반복하며 여러 차례 되돌리기를 거듭했고 몇 시간을 날렸다.


이 사고에서 뼈저리게 배운 게 몇 가지 있다. 첫째, 시크릿은 배포 과정에서 뜻하지 않게 사라지거나 덮어써질 수 있다는 것이다. 그래서 배포 후에는 반드시 핵심 기능이 정상인지 확인해야 했다. 특히 로그인 같은 인증 기능은 배포 직후 실제로 되는지 점검하는 절차를 습관으로 만들었다. 배포가 성공했다는 표시만 믿지 않고, 진짜로 로그인이 되는지 눈으로 확인하는 것이다.


둘째, 시크릿이 사라졌을 때 어떻게 복구하는지 미리 알아 둬야 한다는 것이다. 사고 당시 나는 열쇠를 어떻게 다시 등록하는지 허둥지둥 찾느라 시간을 더 썼다. 그 뒤로는 시크릿 복구 절차를 기록해 두고, 급할 때 바로 실행할 수 있게 준비했다. 사고는 반드시 나쁜 순간에 나기 마련이라, 그때 우왕좌왕하지 않도록 평소에 대비하는 게 중요했다.


셋째, 배포 설정 자체가 위험을 부를 수 있다는 것이다. 내 사고의 원인은 열쇠를 잘못 다룬 게 아니라 배포 환경 설정이 꼬인 데 있었다. 그래서 배포와 관련된 설정은 함부로 건드리지 않고, 바꿀 때는 그 여파를 신중히 살피게 됐다. 시크릿 관리는 열쇠 자체뿐 아니라 그 열쇠가 놓인 환경 전체를 안정적으로 다루는 일이었다. 부품 하나가 아니라 그것을 둘러싼 맥락까지 봐야 했다.


4. 열쇠에도 수명이 있다

시크릿을 안전하게 보관하는 것만큼 중요한 게 주기적으로 교체하는 습관이었다. 아무리 잘 감춰도 열쇠는 시간이 지나며 어떤 경로로든 노출 위험이 쌓인다. 그래서 중요한 열쇠는 이따금 새것으로 갈아 주는 게 좋다. 특히 유출이 의심되는 정황이 조금이라도 있으면 지체 없이 교체해야 한다. 교체 절차를 미리 익혀 두면 유사시 빠르게 대응할 수 있었다.


교체를 염두에 두고 설계하는 것도 중요했다. 열쇠를 바꾸는 게 큰일이 되도록 시스템을 짜 두면 교체를 미루게 되고, 미루다 보면 낡은 열쇠를 계속 쓰게 된다. 반대로 열쇠 교체가 간단한 작업이 되도록 만들어 두면 부담 없이 자주 갈 수 있다. 방어를 실천 가능하게 만드는 것도 설계의 일부였다. 아무리 옳은 방어도 실행하기 번거로우면 결국 방치되기 때문이다.


노출을 감지하는 장치도 도움이 됐다. 나는 핵심 시크릿이 제대로 설정돼 있는지, 없어지지는 않았는지 주기적으로 스스로 점검하는 장치를 두었다. 만약 필수 시크릿이 빠져 있으면 운영자에게 알림이 가도록 했다. 앞서 겪은 열쇠 소실 사고를 다시는 조용히 넘기지 않겠다는 대비였다. 방어가 무너졌을 때 그 사실을 빨리 아는 것만으로도 피해를 크게 줄일 수 있었다.


이런 자동 점검은 사람이 놓치는 부분을 메워 줬다. 1인 운영자는 모든 걸 늘 지켜볼 수 없으니, 중요한 상태를 기계가 대신 감시하고 이상이 생기면 알려 주게 하는 게 현실적이었다. 방어 장치를 만드는 것에서 그치지 않고, 그 방어 장치가 살아 있는지를 감시하는 또 하나의 장치를 두는 것, 이 겹겹의 안전망이 결국 사이트를 조용히 지탱했다.


5. 비밀에서 권한으로

시크릿 관리는 결국 무엇을 누가 알 수 있느냐의 문제였다. 그런데 이 질문은 자연스럽게 다음 주제로 이어진다. 무엇을 누가 할 수 있느냐, 즉 권한의 문제다. 열쇠를 잘 감춰도, 정작 서비스 안에서 어떤 사용자가 무엇을 할 수 있는지를 허술하게 설계하면 다른 문으로 사고가 난다. 로그인한 사용자라고 다 같은 사용자가 아니기 때문이다.


예를 들어 어떤 사용자가 남의 글을 수정하거나 지울 수 있으면 안 된다. 일반 사용자가 운영자만 할 수 있는 기능에 손댈 수 있어도 안 된다. 이런 걸 막으려면 각 사용자가 어떤 역할을 갖는지, 그리고 특정 자원에 대해 그 사용자가 정당한 권한이 있는지를 매 요청마다 확인해야 한다. 인증이 너는 누구냐를 묻는다면, 권한은 너는 이걸 해도 되느냐를 묻는다.


그래서 다음 편에서는 권한 설계를 다루려 한다. 역할을 어떻게 나누고, 어떤 자원의 소유권을 어떻게 확인하며, 남의 권한을 빌려 요청을 위조하는 공격을 어떻게 막는지를 실제 우리 사이트의 구현과 함께 이야기하겠다. 인증이라는 첫 관문을 지나온 사용자에게도 방심하지 않고 매번 자격을 확인하는 것, 그것이 방어의 안쪽에서 마지막까지 지켜야 할 원칙이다.