메인 콘텐츠로 건너뛰기 탐색으로 건너뛰기 푸터로 건너뛰기
기간 한정 디자인 파트너 프로그램. BUSINESS 플랜 평생 무료.

시크릿 관리

배포 시 자격 증명을 포크가 접근할 수 없는 곳에 저장합니다. 설계상 쓰기 전용.

시크릿 관리

포크의 문제는 이겁니다. 포크는 암호화된 이미지의 바이트 단위 복사본입니다. 자격 증명도 모두 포함됩니다. 저장소에 있는 Stripe 라이브 키, 데이터베이스 비밀번호, API 토큰? 포크가 모두 상속합니다. 결국 샌드박스가 실제 고객 카드에 청구하게 됩니다.

올바른 방법은 rdc repo secret입니다. 두 가지 전달 모드, 설계상 쓰기 전용, 그리고 포크는 아무것도 없이 시작합니다. 이 튜토리얼에서는 두 종류를 모두 설정하고, 앱을 배포하여 값이 실제로 컨테이너에 도달하는지 확인하고, 시크릿이 포크를 따라가지 않아 포크가 시작조차 못하는 것을 직접 봅니다.

튜토리얼 보기

함정: 저장소의 .env

A .env file inside the repo image gets cloned by every fork

대부분의 팀은 저장소에 .env를 넣습니다. 당연한 선택입니다.

그런 다음 포크합니다.

포크는 부모 이미지의 바이트 단위 복사본입니다. .env에 있는 모든 것이 포크의 .env에도 있습니다. 포크의 컨테이너가 부팅됩니다. 동일한 Stripe 키를 읽습니다. 프로덕션 자격 증명으로 동일한 Stripe API를 호출합니다. Stripe 입장에서 그 호출은 당신입니다.

정말 최악의 경험입니다. 실제로 겪어본 사람으로서 말씀드립니다.

1단계: env 모드로 시크릿 설정

rdc repo secret set --name my-app --key DB_HOST --value postgres.internal --mode env

먼저, env 모드로 시크릿을 설정해 봅시다. 값은 container 안에서 환경 변수로 적용됩니다. 처음 쓸 때는 별다른 절차가 필요 없지만, 기존 시크릿을 덮어쓸 때는 확인이 필요합니다.

--mode env는 값이 컨테이너 내부에서 환경 변수로 전달되게 합니다. 처음 쓸 때는 별도 절차가 없습니다. 기존 시크릿을 덮어쓸 때만 현재 값을 증명해야 합니다.

2단계: file 모드로 시크릿 설정

rdc repo secret set --name my-app --key STRIPE_KEY --value sk_test_xxx --mode file

file 모드 시크릿을 설정하세요. file 모드는 container 환경을 통해 값을 노출하지 않으며, Docker의 표준 시크릿 메커니즘을 사용해 /run/secrets 하위 파일에 값을 씁니다. 민감한 정보에는 file 모드를 권장합니다.

file 모드는 값을 컨테이너의 환경에 넣지 않습니다. 대신 Docker의 표준 메커니즘을 사용해 /run/secrets/stripe_key에 씁니다. 민감한 것에는 이 방식을 권장합니다.

3단계: 목록 확인

rdc repo secret list --name my-app

지금까지 설정한 것들을 목록으로 확인해 봅시다. 이름과 모드만 표시됩니다. 누가 요청해도 값은 절대 표시되지 않습니다.

이름과 모드가 표시됩니다. 값은 없습니다. 목록은 누가 물어봐도 절대 값을 표시하지 않습니다.

compose에 연결하기

docker-compose.yml을 엽니다. 두 모드를 모두 참조합니다:

services:
  api:
    image: myapp:latest
    environment:
      DATABASE_HOST: ${REDIACC_SECRET_DB_HOST}
    secrets:
      - stripe_key

secrets:
  stripe_key:
    file: /var/run/rediacc/secrets/${REDIACC_NETWORK_ID}/STRIPE_KEY

${REDIACC_SECRET_DB_HOST}env 모드입니다. renet의 compose 래퍼가 배포 시 시크릿 스토어에서 확장합니다.

secrets: 블록은 file 모드입니다. Docker의 표준 메커니즘입니다. 호스트 경로는 ${REDIACC_NETWORK_ID}를 사용하므로 동일한 compose가 부모와 포크 모두에서 작동합니다. 각 포크에는 자체 네트워크 ID가 있습니다.

값을 다시 읽을 수 없습니다

Write-only model: get returns a digest, never the value

이것이 처음 접하는 사람들을 놀라게 하는 부분입니다. 저도 그랬습니다.

4단계: Get은 다이제스트를 반환합니다

rdc repo secret get --name my-app --key STRIPE_KEY

secret get 명령은 값 대신 다이제스트를 반환하며, 평문을 복구하는 플래그는 없습니다. GitHub Actions 모델을 따릅니다. 시크릿은 설계상 쓰기 전용입니다.

다이제스트가 반환됩니다. 값이 아닙니다. 값을 반환하게 하는 플래그가 없습니다. 어디에도 평문을 돌려주는 명령이 없습니다.

이것이 GitHub Actions 모델입니다. 쓰기 전용입니다. --current <value>를 전달하여 전제 조건이 통과되는 것을 확인함으로써 시크릿을 알고 있음을 증명할 수 있습니다. Rediacc에 무엇인지 말해달라고 요청할 수 없습니다.

5단계: 잊어버렸을 때 교체하세요

값을 잃었나요? 들여다보지 마세요. 교체하세요.

rdc repo secret set --name my-app --key STRIPE_KEY --value sk_test_new --mode file --rotate-secret

시크릿 값을 잃어버린 경우, 복구를 시도하지 말고 교체하세요. rotate-secret 플래그는 사전 조건 확인을 건너뛰며, 감사 로그에는 의도적인 교체로 기록됩니다.

--rotate-secret는 전제 조건을 건너뜁니다. 감사 로그는 이것을 교체로 표시합니다. 명확하고 의도적입니다.

이전 값을 기억한다면 대신 --current <old-value>로 증명하세요. 더 안전한 방법입니다. 잘못된 터미널이나 다른 컴퓨터에 있을 때 이 방법이 저를 여러 번 구해줬습니다.

배포하고 전달 확인하기

앱에 도달하지 못하는 시크릿은 그냥 화려한 데이터베이스일 뿐입니다. 배포하고 두 전달 경로를 모두 확인합니다.

6단계: 두 시크릿으로 배포

rdc repo up --name my-app --machine <machine-name>

repo를 배포하세요. compose 파일은 두 시크릿을 모두 사용합니다. env 값은 인터폴레이션으로, file 값은 Docker 시크릿 마운트로 전달됩니다.

7단계: env 시크릿이 도착함

rdc term connect --machine <machine-name> --repository my-app --command 'docker exec app printenv DB_HOST'

container 내부에서 변수를 출력하세요. postgres.internal. env 모드 시크릿이 배포 시점에 앱에 전달되었습니다.

컨테이너가 postgres.internal을 출력합니다. 앱이 실제로 값을 받았고, 배포 시 환경에 주입됐습니다.

8단계: file 시크릿이 도착함

rdc term connect --machine <machine-name> --repository my-app --command 'docker exec app cat /run/secrets/stripe_key'

container 내부에서 /run/secrets/stripe_key를 읽으세요. 교체된 값이 마운트되어 있습니다. 앱은 평문을 받습니다. CLI만 표시를 거부합니다.

교체된 값이 컨테이너 내부 /run/secrets/stripe_key에서 읽힙니다. 쓰기 전용은 사람과 CLI에 적용됩니다. 앱은 Docker가 약속한 위치에서 실제 평문을 받습니다.

포크의 핵심

After fork, the secrets list is empty

함정을 기억하시나요? 저장소를 포크하고 확인합니다.

9단계: 저장소 포크

rdc repo fork --parent my-app --tag test --machine <machine-name>

repo를 fork하세요. fork는 부모의 암호화 이미지를 바이트 단위로 복사한 것입니다.

10단계: 포크는 빈 목록을 표시합니다

rdc repo secret list --name my-app:test

fork의 시크릿을 목록으로 조회하면 빈 결과가 반환됩니다. Stripe 키도, 데이터베이스 비밀번호도, API 토큰도 없습니다. fork는 부모를 사칭할 수 없으며, 이것이 프로덕션 복제를 안전하게 만드는 이유입니다.

비어 있습니다.

포크에는 Stripe 키가 없습니다. 데이터베이스 비밀번호도 없습니다. API 토큰도 없습니다. 포크의 컨테이너는 ${REDIACC_SECRET_STRIPE_KEY}를 보간할 수 없습니다. /var/run/rediacc/secrets/<fork-id>/STRIPE_KEY 파일이 존재하지 않습니다.

포크는 당신인 척 할 수 없습니다.

11단계: 포크는 시작조차 못합니다

rdc repo up --name my-app:test --machine <machine-name>

부모의 compose로 fork를 시작하면 실패합니다. fork의 네트워크 ID 하위에 시크릿 파일이 존재하지 않으므로, Docker가 바인드 마운트를 거부합니다. 프로덕션 자격 증명은 fork를 따라가지 않습니다.

배포가 의도적으로 실패합니다: bind source path does not exist: /var/run/rediacc/secrets/<fork-id>/STRIPE_KEY. 시크릿 파일은 포크의 네트워크 ID가 아닌 부모의 네트워크 ID 아래에 있어서 Docker가 마운트를 거부합니다. 이 실패가 바로 데모입니다. 프로덕션 자격 증명은 실수로도 포크를 따라가지 않습니다.

테스트를 위해 포크에 시크릿이 필요하다면 포크에 명시적으로 샌드박스 값을 설정합니다. 예: rdc repo secret set --name my-app:test --key STRIPE_KEY --value sk_sandbox_yyy --mode file. 이제 포크는 Stripe 샌드박스와 통신하며 정상적으로 시작됩니다. 프로덕션 자격 증명은 프로덕션을 떠나지 않았습니다.

요약

  • rdc repo secret은 자격 증명을 저장소 이미지 외부에 저장합니다.
  • 두 모드 모두 실제로 컨테이너에 도달합니다: env 보간과 /run/secrets.
  • get은 값이 아닌 다이제스트를 반환합니다. 잊어버리면 교체하세요. 들여다보지 마세요.
  • 포크는 빈 목록을 표시하며 부모의 compose조차 시작할 수 없습니다.

포크가 따라올 수 없는 시크릿.


다음: 백업 및 복원.