사람이 사라진 뒤에 남는 시스템: 부고 한 편에서 다시 꺼낸 버스 팩터 점검법
이번에 고른 글은 코드 한 줄 없는 글이다. Stephen Wolfram(Mathematica·Wolfram Language를 만든 그 사람)이 아내 Elise Cawley를 추모하며 쓴 글이고, 제목에 1961–2026과 36 Wonderful Years가 들어가 있다. Hacker News 상단에 올라와 있었다.
먼저 분명히 해둘 것: 원문 본문을 가져오지 못했다. 그래서 글에 어떤 이야기가 담겼는지, 어떤 문장이 인상적이었는지는 쓰지 않는다. 개인의 죽음과 가족사를 추측으로 채우는 건 하면 안 되는 일이다. 내용은 원문을 직접 읽는 쪽을 권한다. 세부 사실은 공식 게시물 확인 필요.
도입: 부고를 읽고 나서 인프라 담당자가 떠올린 질문
이런 글이 HN에 오르면 댓글창은 대체로 애도로 채워지지만, 나는 매번 직업병처럼 다른 생각이 든다. 사람이 갑자기 없어졌을 때, 그 사람에게만 묶여 있던 것들은 어떻게 되는가.
거창한 이야기가 아니다. 5년 굴려보면 이런 일은 생각보다 자주 만난다.
- 퇴사자 GPG 키로만 암호화된
secrets.enc.yaml - 개인 계정 MFA에 묶인 AWS 루트, 도메인 레지스트라, 앱스토어 계정
- 10년 전에 한 사람이 짜고 아무도 안 건드린 배치 스크립트
- 사내 위키에 없고 그 사람 머릿속에만 있던 "장애 나면 이거 먼저 확인" 순서
퇴사는 최소한 예고가 있다. 사고나 병은 없다. 재해복구 계획에 리전 장애는 있는데 사람 장애는 없는 조직이 대다수다. 그래서 이 글은 부고에 대한 감상문이 아니라, 그걸 계기로 실제로 돌려본 점검 절차 정리다.
핵심: 버스 팩터는 "지식"이 아니라 "복호화 가능성"의 문제다
버스 팩터(bus factor)는 보통 "몇 명이 빠지면 프로젝트가 멈추나"로 설명된다. 실무에서 더 정확한 정의는 이렇다.
어떤 자산에 대해, 접근 경로가 단 하나의 사람 계정에만 존재하는가.
지식은 그래도 시간을 들이면 복원된다. 코드를 읽고, 로그를 뒤지고, 재구성한다. 복원 불가능한 건 암호학적으로 잠긴 것이다. 비유하면, 창고 위치를 모르는 건 며칠 걸리는 문제고 창고 열쇠가 세상에 한 개뿐이었던 건 영구 손실이다.
그래서 우선순위는 이 순서가 맞다.
- 복구 불가: 단일 GPG/age 키로 암호화된 시크릿, 개인 TOTP에만 걸린 루트 계정, 개인 소유 도메인
- 복구 비싸다: 문서 없는 수동 운영 절차, 로컬에만 있는 terraform state
- 복구 가능: 코드 이해, 아키텍처 파악
1번부터 손대야 하는데, 실제로는 3번(문서화 스프린트)만 하고 끝내는 조직을 너무 많이 봤다.
실무 관점: 30분이면 돌려보는 점검 3종과 흔한 함정
1) 리포지토리 단독 소유자 스캔
파일별로 커밋한 사람이 한 명뿐인 경로를 뽑는다. 문서화 우선순위 정하는 데 이만한 근거가 없다.
#!/usr/bin/env bash
# bus-factor.sh — 최근 3년간 커밋 저자가 1명뿐인 파일 목록
git ls-files | while read -r f; do
n=$(git log --since="3 years ago" --format='%ae' -- "$f" | sort -u | wc -l)
[ "$n" -eq 1 ] && printf '%s\t%s\n' \
"$(git log -1 --format='%ae' -- "$f")" "$f"
done | sort | uniq -c | sort -rn | head
$ ./bus-factor.sh
47 kim@example.com deploy/legacy-batch/
12 kim@example.com scripts/db-failover.sh
9 park@example.com terraform/modules/vpn/
$ # kim 한 사람이 68개 파일의 유일한 저자 → 여기부터 페어링
주의: uniq -c로 묶으려면 경로를 디렉터리 단위로 자르는 게 실용적이다. 위 출력은 형태 예시로, 숫자는 각자 리포에 따라 완전히 달라진다.
2) 시크릿 복호화 권한자 확인
SOPS를 쓴다면 암호화 파일 메타데이터에 수신자 목록이 그대로 박혀 있다. 이걸 안 보고 넘어가는 팀이 정말 많다.
$ grep -A4 'pgp:' secrets/prod.enc.yaml | grep fp
fp: 9A1B2C3D4E5F60718293A4B5C6D7E8F901234567
$ gpg --list-keys 9A1B2C3D4E5F60718293A4B5C6D7E8F901234567
pub rsa4096 2019-04-02 [SC] [expired: 2024-04-01]
9A1B2C3D4E5F60718293A4B5C6D7E8F901234567
uid [ expired] Former Dev <kim@example.com>
수신자가 한 명이거나, 만료됐거나, 이미 퇴사자면 그 파일은 사실상 시한폭탄이다. 이럴 때 CI에서 튀어나오는 게 이 에러다.
$ sops -d secrets/prod.enc.yaml
Failed to get the data key required to decrypt the SOPS file.
Group 0: FAILED
9A1B2C3D4E5F60718293A4B5C6D7E8F901234567: FAILED
- | could not decrypt data key with PGP key:
| github.com/ProtonMail/go-crypto/openpgp error: Could not
| load secring: open /home/runner/.gnupg/secring.gpg: no
| such file or directory
Recovery failed because no master key was able to decrypt the
file. In order for SOPS to recover the file, at least one key
has to be successful, but none were.
이 메시지를 배포 직전에 처음 보면 그날 릴리스는 끝난 거다. 해결은 결국 수신자를 늘려두는 것뿐이다. 개인 키 대신 KMS/Vault 같은 서비스 키를 1차로 두고, 개인 키는 보조로만 넣는 구성을 권한다.
$ sops updatekeys secrets/prod.enc.yaml # .sops.yaml 규칙 반영
$ sops -d secrets/prod.enc.yaml | head -1
db_password: ENC-was-here
3) 개인에게 묶인 클라우드 접근 경로
$ aws iam generate-credential-report >/dev/null
$ aws iam get-credential-report --query Content --output text \
| base64 -d | awk -F, 'NR==1||$4=="false"||$8=="true"' \
| cut -d, -f1,4,8,9 | column -t -s,
user mfa_active access_key_1_active access_key_1_last_rotated
<root_account> true false N/A
kim false true 2021-03-11T04:22:00+00:00
ci-deployer false true 2025-06-02T11:03:00+00:00
여기서 kim처럼 MFA 없고 4년 넘게 안 돌린 키가 나오면 보안 문제이면서 동시에 승계 문제다. 컬럼 인덱스는 credential report 포맷에 따라 다르니 base64 -d | head -1로 헤더 먼저 확인하는 게 안전하다. 정확한 필드 순서는 AWS 공식 문서 확인 필요.
트레이드오프와 대안
여기서 늘 부딪히는 게 승계 가능성 vs 최소 권한이다. 아무나 프로덕션 시크릿을 열 수 있게 만들면 그건 다른 종류의 사고다. 현실적인 타협안 세 가지:
- break-glass 계정: 평소엔 잠겨 있고 사용 시 알림이 터지는 비상 계정. TOTP 시드를 종이로 인쇄해 금고에 넣는 방식이 여전히 유효하다. 대신 연 1회 실제로 열어보는 훈련이 없으면 무용지물이다.
- 키 분할: Vault unseal key처럼 Shamir 방식으로 나눠 3명 중 2명이 모이면 복구. 운영 부담이 커서 정말 최상위 자산에만 쓴다.
- 소유권 대장: 도메인, 결제 수단, SaaS 관리자, 코드 서명 인증서를 표 하나로 관리. 지루하지만 투자 대비 효과가 가장 크다. 도메인은
whois example.com | grep -i expir정도만 크론에 걸어둬도 최악은 피한다.
흔한 함정 하나 더. 계정을 즉시 비활성화했다가 그 계정이 소유하던 리소스가 같이 죽는 케이스다. Terraform state를 개인 버킷에 두고 있었으면 이런 걸 보게 된다.
$ terraform plan
Error: Failed to get existing workspaces: Unable to list objects in
S3 bucket "kim-tfstate-2020" with prefix "env:/": operation error S3:
ListObjectsV2, https response error StatusCode: 403, api error
AccessDenied: Access Denied
그래서 오프보딩 순서는 "삭제"가 아니라 정지 → 소유 리소스 이전 → 삭제다. 계정을 비활성화하되 30~90일 보존하는 유예 기간을 정책으로 박아두는 게 낫다.
정리
한 줄 요약: 재해복구 시나리오에 "핵심 인력 한 명이 오늘부로 영구히 부재" 항목을 넣고, 지식보다 암호학적 단일 소유권을 먼저 없애라.
언제 해야 하나. 팀이 5명을 넘어가고 프로덕션 시크릿이 생긴 순간부터다. 이미 늦은 팀이라면 위 3종(단독 저자 스캔 / SOPS 수신자 점검 / MFA·오래된 키 리포트)만 이번 주에 돌려봐도 대체로 한두 개는 걸린다. 걸리면 그게 당신 조직의 진짜 SPOF다.
그리고 이 글의 출발점이 된 원문은 기술 문서가 아니다. 링크는 아래에 두었고, 애도를 전한다.