홈시리즈멘토링

© 2026 정기창. All rights reserved.

본 블로그의 콘텐츠는 CC BY-NC-SA 4.0 라이선스를 따릅니다.

☕후원하기소개JSON Formatter러닝 대기질개인정보처리방침이용약관

© 2026 정기창. All rights reserved.

콘텐츠: CC BY-NC-SA 4.0

☕후원하기
소개|JSON Formatter|러닝 대기질|개인정보처리방침|이용약관

노트북을 잃어버리면 무엇이 사라지는가 (2) — 전부 무료로 짠 3계층 백업 아키텍처

정기창·2026년 7월 31일

지난 편에서 저는 노트북과 함께 사라질 '빨강 자산'—서버·플랫폼·네트워크로 들어갈 수단들—을 골라냈습니다. 값을 한 번도 출력하지 않고 만든 목록이 손에 있었습니다. 이제 남은 질문은 하나였습니다. 이것들을 어디에, 어떻게 넣어둘 것인가.

결론부터 적자면, 저는 한 곳에 모으는 대신 세 개의 계층으로 나눴습니다. 설계 원칙은 딱 하나였습니다. 계층마다 단일 실패점이 서로 다른 곳에 있도록. 어느 한 곳이 무너져도 나머지로 복구가 되도록 만드는 것—보안에서 흔히 계층 방어(defense in depth)라고 부르는 발상을, 백업에 그대로 옮겨온 셈입니다.

세 개의 계층, 세 개의 서로 다른 실패점

먼저 전체 그림을 표로 정리해 두겠습니다. 핵심은 맨 오른쪽 두 열입니다. 각 계층이 무엇 때문에 무너질 수 있고, 되살아나기 위해 무엇에 의존하는지가 전부 다릅니다.

계층 담는 것 단일 실패점 복구 전제
L0 부트스트랩 다른 모든 걸 여는 소수의 열쇠 패스워드 매니저 계정 마스터 비밀번호 + 2FA
L1 시크릿 본체 흩어진 시크릿 값 전체 (암호화된 채) private git 호스팅 L0가 보관한 복호화 키
L2 운영 env 스냅샷 배포 플랫폼 운영 환경변수 (암호화된 채) git 호스팅 + 배포 플랫폼 계정 L0가 보관한 복호화 키

L0 — 다른 모든 걸 여는 소수의 열쇠

가장 밑바닥 계층은 무료 클라우드 패스워드 매니저에 두었습니다. 무제한 기기 동기화를 제공하고, 마스터 비밀번호와 2FA만으로 계정을 복구할 수 있는 종류입니다. 여기에는 시크릿을 전부 넣지 않았습니다. 오직 다른 모든 것을 여는 소수의 열쇠만 넣었습니다. 암호화 마스터 키, SSH 개인키들, 2FA 복구코드, 그리고 나머지를 되살릴 때 꼭 필요한 핵심 토큰 몇 개.

여기서 무료 티어 특유의 제약을 하나 만났습니다. 파일 첨부가 안 된다는 것. SSH 개인키는 파일인데 파일을 못 붙이니 난감했습니다. 다만 방법은 단순했습니다. 개인키는 결국 텍스트(PEM 형식)이므로, 파일 대신 보안 메모(secure note)에 PEM 텍스트를 그대로 붙여넣는 것으로 우회했습니다. 복구할 때는 그 텍스트를 다시 파일로 저장하고 권한만 600으로 맞춰주면 됩니다.

L1 — 시크릿 본체를 값만 암호화해서 버전관리

두 번째 계층은 흩어진 시크릿 값 전체입니다. 이건 양이 많아서 패스워드 매니저에 일일이 넣기 어렵습니다. 대신 SOPS와 age로 값만 암호화해 private git에 버전관리하기로 했습니다. SOPS는 설정 파일에서 키 이름은 남기고 값만 암호문으로 바꿔주는 도구고, age는 그 암호화에 쓰는 가볍고 현대적인 키 방식입니다.

여기서 두 가지를 결정했습니다. 첫째, 흩어진 레포마다 시크릿을 커밋하는 대신 중앙 백업 레포 하나에 홈 디렉토리 구조를 그대로 미러링하기로 했습니다. 복구할 때 경로 그대로 풀어놓기만 하면 되도록요.

~/secrets-vault/
├─ .ssh/
│  └─ config.enc
├─ .config/
│  └─ <도구>/secrets.env.enc
└─ projects/
   └─ <클라이언트 프로젝트>/.env.enc

둘째—그리고 이게 설계의 핵심인데—복호화 열쇠(age 개인키)를 이 L1 레포에 넣지 않았습니다. age 개인키는 L0(패스워드 매니저)가 보관합니다. 그래서 만에 하나 이 git 레포가 통째로 유출돼도, 공격자가 손에 쥐는 것은 열리지 않는 암호문 덩어리뿐입니다. 자물쇠와 열쇠를 같은 상자에 넣지 않는다는, 지극히 당연하지만 지키기는 쉽지 않은 원칙입니다.

# age 키쌍 생성 — 개인키 파일은 L0로 옮기고, 레포에는 절대 커밋하지 않습니다.
age-keygen -o age-key.txt        # 이 파일이 곧 복호화 열쇠. L0에 보관.

# 값만 암호화 (공개키를 recipient로 지정). 키 이름은 남고 값만 암호문이 됩니다.
sops --encrypt --age <age-공개키> secrets.env > secrets.env.enc

L2 — 운영 환경변수 스냅샷과, 뜻밖의 drift 발견

세 번째 계층은 배포 플랫폼(PaaS)에 올라가 있는 운영 환경변수입니다. 대부분의 배포 플랫폼은 REST API로 특정 앱의 환경변수를 조회할 수 있습니다. 그걸 받아서 L1과 똑같은 방식으로 암호화해 백업했습니다.

# 배포 플랫폼 API로 운영 환경변수를 받아 그대로 암호화합니다.
curl -s -H "Authorization: Bearer $TOKEN" \
  "https://<paas-host>/api/apps/<app>/envs" \
  | sops --encrypt --input-type json --output-type json /dev/stdin \
  > l2/<app>.env.enc

그런데 이 작업에는 예상치 못한 부수효과가 있었습니다. 로컬의 .env와 운영 env를 나란히 놓고 키 이름만 비교해봤더니, 양쪽이 완전히 같지 않았습니다. 로컬에만 있고 운영에는 없는 키가 실제로 있었던 것입니다. 다시 말해, 그 값은 제 노트북이 유일본이었습니다. 백업 태세를 점검하려던 작업이, 뜻하지 않게 drift(로컬과 운영의 어긋남)까지 잡아준 셈입니다.

# 값은 빼고 '키 이름'만 뽑아 로컬 ↔ 운영을 비교합니다 (drift 감지).
comm -3 \
  <(grep -oE '^[A-Z_][A-Z0-9_]*' local.env | sort -u) \
  <(jq -r 'keys[]' prod-envs.json | sort -u)

왜 셀프호스트 볼트를 고르지 않았나 — 순환 의존

사실 더 '있어 보이는' 선택지가 있었습니다. 자체 서버에 오픈소스 패스워드 매니저를 올리는 셀프호스트 볼트입니다. 내 데이터를 내 인프라에 둔다는 점이 꽤 매력적이었습니다. 그럼에도 불구하고 저는 부트스트랩 계층(L0)만큼은 여기에 두지 않기로 했습니다.

이유는 순환 의존(circular dependency) 때문이었습니다. 지금 제가 풀려는 문제 자체가 "서버 접근 수단을 잃는 것"입니다. 그런데 복구의 열쇠를 바로 그 서버 위에 올려두면, 서버 접근을 잃는 순간 그 열쇠를 담은 볼트도 함께 잠깁니다. 정작 복구가 필요한 상황에서 복구 도구가 같이 사라지는 것입니다. 닭이 없으면 달걀을 못 얻고 달걀이 없으면 닭을 못 얻는, 전형적인 부트스트랩 문제였습니다.

부트스트랩 계층만큼은 반드시 내 인프라 바깥, 계정만으로 복구되는 곳에 있어야 한다. 그래야 내 인프라 전체가 잠겨도 바깥에서 열쇠를 꺼내 되돌아올 수 있습니다.

L1과 L2는 셀프호스트든 어디든 상관없습니다. 어차피 암호문이고, 그걸 여는 열쇠는 바깥(L0)에 있으니까요. 하지만 그 열쇠를 쥔 L0는 반드시 내 손이 닿는 마지막 지점, 즉 계정 복구만으로 되살아나는 곳에 있어야 했습니다.

OS 키체인이 "동기화되니 복구되겠지"라는 착각

마지막으로, 이 과정에서 하마터면 그냥 넘어갈 뻔한 함정을 하나 적어둡니다. OS 기본 키체인에 대한 막연한 믿음이었습니다. "어차피 클라우드로 동기화되니 노트북이 사라져도 복구되겠지"라는 생각 말입니다.

그런데 이건 절반만 맞는 얘기였습니다. 애석하게도 CLI로 넣은 키체인 항목은 클라우드 키체인 동기화 대상이 아닐 수 있습니다. macOS에서 security add-generic-password 같은 명령으로 넣은 항목이 특히 그렇습니다. 눈에는 똑같이 키체인에 들어가 있는 것처럼 보이지만, 동기화되는 항목과 기기에만 남는 항목이 섞여 있는 것입니다. 시스템 전체 백업이 없다면, 이런 항목들은 조용히 노트북과 함께 사라집니다.

결국 "동기화될 것"이라는 믿음은 검증 전까지는 가정에 불과하다는 생각이 들었습니다. 그래서 키체인에 의존하던 항목들도 빠짐없이 L0나 L1로 옮겨, 눈에 보이는 계층 안에 두기로 했습니다.

다음 편으로

여기까지가 구조입니다. 그런데 구조를 그려놓는 것과, 그 구조가 실제로 복구를 보장하는 것은 또 다른 이야기였습니다. 백업을 짜는 내내 저를 붙잡은 것은 오히려 사소해 보이는 함정들—clone하면 된다는 착각, 원격 URL에 박힌 토큰, 복호화가 안 되는 키 경로 같은—이었습니다. 다음 편에서는 이 백업을 실제로 믿을 수 있게 만들기까지 마주친 함정들을, 문제·원인·해결·검증의 순서로 하나씩 적어보겠습니다.

백업SOPSage재해복구개발보안패스워드매니저

관련 글

노트북을 잃어버리면 무엇이 사라지는가 (1) — 값은 살아있는데 못 들어가는 역설

노트북 한 대에 모든 개발 자격증명이 몰려 있다는 걸 뒤늦게 깨달았습니다. 시크릿 값의 생존과, 그 값에 닿을 수단의 생존은 전혀 다른 문제였습니다. 값을 한 번도 출력하지 않고 자산을 전수 조사한 기록입니다.

관련도 96%

운영 워드프레스를 건드리지 않고 로컬 Docker로 클론해 개발하기

운영 중인 워드프레스를 직접 수정하는 건 위험합니다. 운영에는 읽기만 하면서 DB와 파일을 로컬 Docker로 복제해 안전한 개발 환경을 만든 과정과, 멀티사이트 도메인·PHP 버전·문자 인코딩에서 마주친 함정을 정리했습니다.

관련도 91%

쿠키 없이 24시간 고유 방문자를 추정하는 방법 (3편)

쿠키도 localStorage도 쓰지 않고 하루 안에서만 같은 독자를 알아보는 방법을 정리했습니다. Plausible의 daily salt 해시를 HMAC 기반 deterministic 방식으로 재구성하면서, 서버 재시작 안전성과 cross-day 추적 불가능성을 어떻게 확보했는지 기록했습니다.

관련도 91%