LabHub
배우기 러닝패스 코스

Working With Customer Data

Having Permission Does Not Mean You Should Look

LabHub 에서 이어서 보기

한국어 원문으로 표시합니다.

한 줄 요약

FDE 는 고객의 실제 데이터에 손이 닿는 자리입니다. 기술보다 먼저 배워야 할 것은 최소한만 보고, 최소한만 옮기고, 흔적을 남기는 습관입니다.

Concept map: 최소한만 보고, 최소한만 옮기고, 흔적을 남기는 · 편의에서 · 1. 필요한 열만 가져온다 · 2. 식별자는 목적별로 가명처리한다

왜 이게 필요했나

조사하다 보면 자연스럽게 이런 명령을 치게 됩니다.

SELECT * FROM orders WHERE status = 'FAILED' LIMIT 100;

여기에는 주문번호만 있는 게 아닙니다. 이름, 전화번호, 주소, 때로는 결제 정보가 함께 나옵니다. 그리고 그 결과가 터미널 스크롤백에, 캡처 이미지에, 슬랙 메시지에, 여러분의 노트북 임시 파일에 남습니다.

사고는 악의에서보다 편의에서 생깁니다. "빨리 보려고 CSV 로 뽑아서 메일로 보냈다" 가 실제 사고 보고서에 가장 많이 등장하는 문장입니다.

네 가지 원칙

1. 필요한 열만 가져온다 SELECT * 대신 조사에 필요한 열만 지정합니다. 원인 분석에 이름과 주소가 필요한 경우는 거의 없습니다.

-- 나쁨
SELECT * FROM orders WHERE status='FAILED';
-- 좋음
SELECT id, created_at, status, error_code FROM orders WHERE status='FAILED';

2. 식별자는 목적별로 가명처리한다 동일인 연결이 꼭 필요하면 정규화한 식별자에 목적별 비밀 키를 사용한 HMAC-SHA-256을 적용하고 충분한 길이의 출력을 유지합니다.

uid = HMAC_SHA256(secret_from_kms, normalize(email))

이 값은 익명화된 값이 아니라 가명화된 식별자입니다. 비밀 키나 주변 데이터가 노출되면 다시 연결될 수 있으므로 원본과 같은 수준으로 접근을 통제합니다. 시스템 간 연결이 승인된 경우에만 같은 목적 키를 공유하고, 서로 연결되면 안 되는 분석에는 목적별로 다른 키를 사용합니다.

3. 밖으로 내보내지 않는다 데이터는 있던 자리에서 다룹니다. 로컬로 내려받거나 메신저로 보내는 순간 통제 밖입니다. 부득이하면 집계된 형태로만 옮깁니다 — 원본 100행 대신 "실패 사유별 건수" 표 한 장.

4. 남긴다 언제 무엇을 조회했는지 기록합니다. 나중에 "누가 이걸 봤나" 라는 질문이 왔을 때 스스로를 방어하는 유일한 수단입니다. 대부분의 시스템에 감사 로그가 있고, 없다면 여러분의 작업 노트가 그 역할을 합니다.

마스킹은 되돌릴 수 있는지로 나뉜다

방식 되돌리기 언제
삭제 불가 조사에 불필요한 열
목적별 키를 쓴 충분히 긴 HMAC 키 없이는 곤란 승인된 범위에서 동일인 묶기가 필요할 때
부분 마스킹 (010-****-1234) 불가하지만 유추 가능 육안 확인이 필요할 때
토큰화(매핑 보관) 가능 나중에 원본이 필요할 때
암호화 가능 이동·보관이 불가피할 때

일반 해시나 공개 솔트만으로는 안전하지 않습니다. 전화번호와 이메일은 후보를 만들어 전수 대입하기 쉬우며, 8자리처럼 출력을 자르면 충돌 위험도 커집니다. 레코드마다 다른 솔트는 같은 사람을 같은 값으로 묶을 수 없으므로, 반복 가능한 연결이 필요할 때는 접근 통제된 목적별 비밀 키와 충분히 긴 HMAC을 사용합니다.

테스트 데이터를 만들 때

운영 데이터를 복사해 테스트에 쓰는 것은 가장 흔하고 가장 위험한 관행입니다. 꼭 필요하다면 구조는 유지하고 값은 바꾸는 방식으로 만듭니다 — 이름은 사전에서, 이메일은 도메인을 example.com 으로, 금액은 분포를 유지하며 노이즈 추가. 이렇게 하면 재현에 필요한 성질(길이 분포, 중복, 결측)은 남고 개인정보는 사라집니다.

현장에서 만나는 모습

로그와 오류 보고가 가장 흔한 유출 경로

개인정보 사고는 데이터베이스가 털려서만 나지 않는다. 정상적으로 동작하는 시스템이 성실하게 남긴 기록에서 더 자주 샌다.

요청 전체를 찍는 습관이 가장 위험하다. 디버깅하려고 넣은 한 줄이 운영에 남으면, 주민등록번호와 카드번호가 로그 저장소로 흘러 들어간다. 로그는 대개 암호화되지 않고, 보존 기간이 길고, 조회 권한이 넓다.

# 이렇게 두면 어느 날 반드시 샌다
log.info("요청: %s", request.json())

# 필요한 것만, 식별자는 해시로
log.info("결제 요청 user=%s amount=%s", hash_id(user.id), amount)

오류 추적 도구는 지역 변수를 함께 보낸다. 예외가 난 프레임의 변수를 담아 보내는 것이 그 도구의 장점인데, 그 프레임에 비밀번호나 토큰이 있으면 그대로 따라간다. 보내기 전에 거르는 자리를 반드시 둔다.

def before_send(event, hint):
    for k in ("password", "token", "authorization", "ssn", "card"):
        _scrub(event, k)
    return event

URL 에 개인정보를 넣지 않는다. 질의 문자열은 접근 로그, 프록시 로그, 브라우저 기록, Referer 헤더에 남는다. 같은 값이라도 본문에 담으면 이 네 곳에 남지 않는다.

세 번째 유출 경로는 사람이다. 장애를 조사하다가 운영 데이터를 노트북으로 내려받고, 사내 메신저에 표를 붙여 넣고, 스크린샷을 문서에 넣는다. 기술적 통제로는 막기 어려우므로 애초에 내려받을 이유를 없애는 쪽이 실효가 있다: 읽기 전용 조회 화면, 마스킹된 관리자 화면, 질의 결과를 자동으로 지우는 분석 환경.

남긴 기록은 언젠가 요청받는다. 어떤 로그에 무엇이 들어가는지 목록으로 관리하고 보존 기간을 정해 둔다. 삭제 요청이 왔을 때 데이터베이스만 지우고 로그와 백업에 남아 있다면 지운 것이 아니다. 처음부터 안 남기는 것이 지우는 것보다 언제나 싸다.

이어지는 실습에서 할 것

개인정보가 섞인 주문 200행을 받아, 밖으로 내보낼 한 장을 만드는 데까지 갑니다. 네 원칙을 각각 파일로 남기고, 마지막에는 반출 전 검사기를 직접 만듭니다.

채점기가 그 검사기를 유출본과 깨끗한 파일에 각각 돌립니다. 전부 막는 검사기도, 전부 통과시키는 검사기도 떨어집니다 — 오탐이 나면 아무도 쓰지 않고, 놓치면 있으나 마나이기 때문입니다.