사용자 개인정보 삭제
사용자를 익명화하면 계정에서 이름과 이메일 주소가 지워지고, 로그인 연결과 세션이 제거되며, 그 사람의 감사 기록에 남은 접속 정보가 지워집니다. 되돌릴 수 없고, 한 사람이 다른 사람에 대해 요청하는 일입니다.
이 문서는 그 절차입니다. 이 작업이 지우지 않는 것도 함께 적습니다. 무언가를 남긴 채 성공했다고 보고하는 삭제는 무엇을 남겼는지 밝히는 삭제보다 나쁘기 때문입니다.
요청과 승인은 super_admin, 이행은 데이터베이스 소유자 자격증명을 가진 운영자입니다. 두 역할은 서로 다른 사람이 서로 다른 일을 하는 것이고, 그것이 의도입니다.
버튼 하나로 만들지 않은 이유
계정 행 자체를 지울 수는 없습니다. 감사 기록이 그 행을 참조하고 있고, 그 참조가 "누가 무엇을 했는가"를 증거로 만듭니다. 사용자를 지우면 그 기록이 함께 사라지거나, 이름이 있던 자리가 "누군가"로 바뀝니다. 그래서 계정은 내용만 비운 채 남습니다.
감사 기록 자체는 추가만 가능하고, 데이터베이스 트리거가 그것을 강제합니다. 거기 남은 접속 정보를 지우려면 그 규칙에 예외가 필요한데, 그 예외는 데이터베이스 소유자만 실행할 수 있는 SECURITY DEFINER 함수입니다. 애플리케이션은 그 자격증명을 갖지 않습니다. docker-compose.yml이 런타임 컨테이너에 넘기지 않기 때문에, 포털이 침해되더라도 자기가 한 일의 기록을 고쳐 쓸 수 없습니다.
여기서 두 가지가 따라 나오고, 둘 다 불편이 아니라 요점입니다.
- 승인은 제품 안에서, 이행은 서버에서 사람이 직접 합니다.
- 그 사이에는 아무것도 고장 나지 않은 채 누군가가 기다리는 구간이 생깁니다.
세 단계
1. 요청 열기
최고 관리자 누구나 대상자에 대해 요청을 엽니다. 이 단계에서 지워지는 것은 없습니다.
POST /v1/user-anonymisation
{ "subject_user_id": "...", "reason": "..." }
최고 관리자가 자기 자신에 대해 요청할 수는 없습니다. 이 통제의 핵심이 서로 다른 두 사람의 동의인데, 자기 요청은 설득할 사람을 하나로 줄입니다. reason은 적은 그대로 저장되므로 연락처를 적지 마십시오.
요청은 7일 동안 승인할 수 있고, 만료를 알리는 통지는 없습니다. 알림이 가지 않고 만료된 요청은 어느 화면에도 나타나지 않습니다. 요청을 연 사람은 아직 결정을 기다리는 중이라고 믿는 동안 삭제는 이행되지 않은 채 남고, 그에 걸린 기한이 지나갑니다. 아직 결정되지 않은 요청을 주기적으로 확인하는 일을 조직의 운영 절차에 넣으십시오. 포털이 알려 주지 않습니다.
같은 대상자에 대해 새 요청을 열면 만료된 요청은 정리되므로, 아무도 결정하지 않은 요청 때문에 대상자가 영구히 막히지는 않습니다.
2. 다른 최고 관리자가 승인
POST /v1/user-anonymisation/{request_id}/approval
승인자는 요청자와도 대상자와도 달라야 합니다. 서비스에서 확인하고 데이터베이스 제약으로 한 번 더 막습니다. 나중에 다른 호출 지점이 생겨도 잊어버려서 건너뛸 수 없게 하기 위해서입니다.
승인해도 아직 지워지는 것은 없습니다.
3. 운영자가 명령 실행
승인은 아무것도 예약하지 않습니다. 삭제는 사람이 이 명령을 실행할 때만 이루어지고, 대신 실행해 주는 것은 없습니다.
이 단계를 조직의 운영 절차에 넣으십시오. 포털이 시간이 되었다고 실행하지 않습니다.
docker-compose -f docker-compose.yml exec -T \
-e DATABASE_URL_OWNER="$(grep ^DATABASE_URL_OWNER= .env | cut -d= -f2-)" \
-e SUBJECT_USER_ID=... \
-e CONFIRM=yes \
backend python -m scripts.anonymise_user
승인된 요청이 없으면 명령이 거부하고, 데이터베이스도 따로 거부합니다. CONFIRM=yes를 요구하는 이유는, 두 사람이 대상자에 대해 동의한 것과 운영자가 지금 이 배포에서 이 식별자를 대상으로 실행할 뜻이 있는 것은 다른 문제이기 때문입니다.
명령이 하는 일은 모두 한 트랜잭션 안에서 이루어집니다. 거부되면 절반만 지워진 상태로 남지 않습니다.
그 사이를 보는 화면
관리 → 상태 화면에 "이행을 기다리는 익명화" 패널이 있습니다. 승인됐지만 아직 실행되지 않은 요청을 오래된 순으로, 대기 일수와 함께 보여 줍니다. 7일이 지나면 기한 초과로 표시합니다. 그 시점이면 결정을 기다릴 수 있었던 기간보다 이행을 기다린 기간이 더 길어진 것입니다.
비어 있을 때 이 패널은 초 록이 아니라 무채색입니다. 무언가를 확인해서 이상이 없는 것이 아니라, 갚을 것이 없는 것이기 때문입니다.
2인 승인이 막는 것과 막지 못하는 것
다른 곳의 표현이 설계보다 강한 약속으로 읽힐 수 있어 정확히 적습니다.
요청 행은 데이터베이스가 확인합니다. 그 대상자에 대한 승인된 요청이 없으면 스크럽을 거부합니다. 이것은 행이 존재하는지를 보는 것이지 두 사람이 동의했음을 증명하는 것이 아닙니다. 절차가 동작하려면 애플리케이션이 그 테이블에 쓸 수 있어야 하고, 따라서 포털을 통해 SQL 실행에 도달한 것은 무엇이든 approved라고 적힌 행을 넣고 실재하는 최고 관리자 두 명의 이름을 적을 수 있습니다.
그 뒤를 받치는 것이 셋입니다. 제품을 거친 요청은 추가만 가능한 감사 로그에 create와 update를 남기고, 운영자 명령은 둘 다 없는 요청을 거부하므로 직접 SQL로 만든 행은 실행되지 않습니다. 미이행 목록이 요청자와 승인자를 표시하므로 운영자는 되돌릴 수 없는 일을 하기 전에 물어볼 사람이 둘 있습니다. 그리고 그 명령은 실행 중인 포털이 결코 받지 않는 자격증명을 가진 사람이 실행합니다.
요청 행에 애플리케이션 비밀키로 서명하는 방안은 검토했고 채택하지 않았습니다. 그 방식이 방어하는 것은 데이터베이스에는 쓸 수 있지만 애플리케이션 설정에는 닿지 못하는 공격자인 데, 운영자 명령이 그 키를 같은 호스트에서 읽어야 하므로 키가 결국 그으려던 경계 위에 놓입니다. 또 행을 위조하는 비용만 올릴 뿐 그 행이 뜻하는 바를 바꾸지 못하고, 이 기능이 막으려는 상황을 새로 만듭니다. 키를 회전하면 이전에 승인된 삭제가 실행되지 않은 채 미이행 목록에 남아 기한을 넘깁니다.
지워지는 것
| 위치 | 지워지는 내용 |
|---|---|
users | 이메일(연락 불가능한 대체 값으로 교체), 이름, 비밀번호 해시. 계정은 비활성화됩니다. |
oauth_identities | 연결된 모든 공급자. 공급자가 갖고 있던 주소 사본도 함께 지워집니다. |
refresh_tokens, password_reset_tokens | 전부. 사용 중인 세션이 끊깁니다. |
saved_searches | 대상자가 직접 작성한 저장된 검색. |
audit_logs | 대상자가 수행자로 기록된 행의 ip와 user_agent. |
| 개인 팀 | 개인 조직에 속하고 대상자가 구성원인 팀의 이름을 Personal team (<식별자 앞부분>)으로 바꾸고 설명을 비웁니다. |