이 체크리스트를 사용해 어떤 값이 런타임 환경에 속하는지, 어떤 값이 생성되어 데이터베이스에 저장되는지, 어떤 값이 플러그인에 의해 저장되는지 확인하세요. 각 섹션에서는 교체가 실행 중인 사이트에 어떤 영향을 주는지도 설명합니다.
Node.js에서는 런타임 비밀을 호스트의 비밀 관리자에 넣어 프로세스가 시작될 때 process.env에 들어가도록 하세요. Worker에서는 wrangler secret put을 사용하세요. 비밀 값을 astro.config.mjs, wrangler.jsonc, import.meta.env에 넣지 마세요. Vite가 빌드 시점의 값을 서버 번들에 포함시킬 수 있습니다.
개요
| 비밀 | 출처 | 저장 위치 | 키를 잃었을 때의 영향 |
|---|---|---|---|
EMDASH_ENCRYPTION_KEY | 운영자(emdash secrets generate) | 환경 변수 / Worker 시크릿에만 저장 | 일치하는 키를 복원할 때까지 암호화된 플러그인 설정을 읽을 수 없음 |
| 미리보기 비밀 | 자동 생성(환경 변수로 재정의 가능) | options 테이블(emdash:preview_secret) | 이미 발급된 미리보기 링크는 작동하지 않음. 새 링크는 문제없음 |
| IP 솔트 | 자동 생성(환경 변수로 재정의 가능) | options 테이블(emdash:ip_salt) | 과거 댓글 속도 제한의 연속성이 초기화됨 |
| 세션 및 API 토큰 | 세션/토큰마다 생성 | 세션 저장소 / 데이터베이스(해시만) | 없음 — 평문은 저장되지 않음 |
| OAuth 제공자 자격 증명 | 사용자(Google, GitHub 또는 Microsoft Entra 콘솔) | 환경 변수 | 교체할 때까지 해당 제공자를 통한 로그인이 중단됨 |
| Turnstile 비밀 | 사용자(Cloudflare 대시보드) | 환경 변수 | 댓글 CAPTCHA 검증이 실패함 |
| S3 자격 증명 | 사용자(스토리지 제공자) | 런타임 환경 | 교체할 때까지 미디어 업로드/다운로드가 실패함 |
| 플러그인 비밀 | 사용자(관리자 설정 UI) | 암호화된 데이터베이스 설정 | 일치하는 암호화 키를 복원하거나 값을 다시 입력 |
| CLI 자격 증명 | emdash login 디바이스 플로우 | ~/.config/emdash/auth.json(모드 0600) | 디바이스 플로우를 다시 실행 |
| 레지스트리 CLI 자격 증명 | emdash-plugin atproto OAuth | ~/.emdash/oauth/, ~/.emdash/credentials.json(모드 0600) | 다시 로그인. ID는 PDS에 있음 |
암호화 키
EMDASH_ENCRYPTION_KEY는 type: "secret"으로 선언된 플러그인 설정을 암호화합니다. EmDash는 AES-GCM을 사용하며, 플러그인 ID와 설정 키를 인증 데이터로 사용합니다. 형식이 잘못된 값은 운영자용 시작 메시지를 생성하고, 암호화된 플러그인 설정이 필요한 작업은 안전하게 실패합니다. 관련 없는 사이트 요청은 계속 작동합니다.
다음 명령은 올바른 형식의 값을 생성합니다. 배포에서 이 변수를 사용한다면 런타임 환경이나 Worker 시크릿으로 저장하세요.
npx emdash secrets generate
# emdash_enc_v1_<43 base64url chars>
# Cloudflare:
wrangler secret put EMDASH_ENCRYPTION_KEY
형식은 emdash_enc_v1_ 뒤에 패딩 없는 base64url로 표현된 32바이트 무작위 값이 이어지는 것입니다. 이 값은 운영자가 제공하며 데이터베이스에 저장되지 않습니다. 비밀 관리자에 보관하고, 복구용 사본을 별도로 보관하세요.
키를 교체하려면 새 값을 앞에 추가하고 이전 값은 쉼표 뒤에 남겨 두세요.
EMDASH_ENCRYPTION_KEY=emdash_enc_v1_<new-key>,emdash_enc_v1_<old-key>
EmDash는 첫 번째 키로 새 값과 다시 저장된 값을 암호화하고, 저장된 kid 지문에 따라 읽을 때 이전 키를 선택합니다. 이전 키를 제거하기 전에 모든 플러그인 비밀을 다시 저장하고, 새 키만 있는 배포에서 해당 연동을 검증하세요. EmDash는 현재 어떤 키 ID가 아직 사용 중인지 보고하지 않으므로, 다시 저장한 자격 증명의 목록을 직접 관리하고 모든 연동이 검증될 때까지 이전 키를 제거하지 마세요.
생성된 사이트 비밀
두 가지 비밀은 처음 사용할 때 자동으로 생성되어 options 테이블에 영구 저장되므로, 요청, 배포, 격리 환경 간에도 안정적으로 유지됩니다. 생성은 원자적입니다. 동시에 발생하는 콜드 스타트도 같은 값으로 수렴합니다.
미리보기 비밀
미리보기 URL에 서명(HMAC)하는 데 사용됩니다. emdash:preview_secret으로 저장되며, 32바이트 무작위 값을 base64url로 인코딩한 것입니다.
- 재정의: 여러 프로세스에서 같은 비밀이 필요하거나 감사 목적으로 고정하고 싶다면
EMDASH_PREVIEW_SECRET(이전 별칭:PREVIEW_SECRET)을 설정하세요. 환경 변수는 항상 저장된 값보다 우선합니다. - 교체:
emdash:preview_secret행을 삭제하거나(또는 환경 변수를 변경하고) 다시 배포하세요. 영향: 이전에 발급된 미리보기 링크는 더 이상 검증을 통과하지 못합니다. 그 외에는 영향이 없습니다. 다음 미리보기 요청 시 새 비밀이 생성됩니다(또는 환경 변수에서 읽습니다). - 잃어버린 경우: 복구할 수 없는 것은 없습니다. 미리보기 링크는 설계상 수명이 짧습니다.
미리보기 URL이 어떻게 만들어지고 검증되는지는 미리보기 가이드를 참고하세요.
IP 솔트
댓글 작성자 IP 주소의 SHA-256 해시(댓글의 ip_hash)에 솔트를 더해 댓글 속도 제한에 사용합니다. emdash:ip_salt로 저장됩니다. 사이트마다 다르므로 EmDash 설치 간에 해시를 서로 연관 지을 수 없습니다.
- 재정의:
EMDASH_IP_SALT를 설정하세요. 하위 호환을 위해EMDASH_AUTH_SECRET/AUTH_SECRET도 참조됩니다. 이전에 이 값들에서 솔트를 파생하던 설치는 해시가 안정적으로 유지됩니다. - 교체: 환경 변수를 변경하거나
emdash:ip_salt행을 삭제한 뒤 다시 배포하세요. 영향: 새 댓글 제출은 다른 값으로 해시되므로 모든 사용자의 속도 제한 카운트가 새로 시작됩니다. 기존 댓글과 저장된 해시는 영향을 받지 않습니다. - 잃어버린 경우: 데이터 손실은 없습니다. 속도 제한의 연속성만 초기화됩니다.
세션 및 API 토큰
- 세션은 Astro의 세션 저장소를 사용합니다(Cloudflare에서는 Workers KV, Node에서는 파일 시스템). 저장소가 구성되지 않은 어댑터(예: Vercel)는
astro.config.mjs에서 세션 드라이버를 구성해야 합니다. 구성하지 않으면 세션 드라이버가 필요하다는 오류와 함께 로그인이 실패합니다. 쿠키에는 불투명한 세션 ID가 담기며, 관리할 서명 비밀은 없습니다. 로그아웃하면 세션이 종료됩니다. 모든 사용자를 강제로 다시 로그인시키려면 세션 저장소(예: KV 네임스페이스)를 비우세요. - API 토큰(접두사
ec_pat_,ec_oat_,ec_ort_)은 불투명한 256비트 무작위 값이며, SHA-256 해시만 저장됩니다. 평문은 생성할 때 한 번만 표시됩니다. 관리자 화면에서 취소하고 다시 만들어 교체하세요. - 초대, 매직 링크, 복구 토큰은 일회용이며 SHA-256 해시로
auth_tokens에 저장되고 시간 제한이 있습니다(초대 7일, 매직 링크 15분).
미리 백업하거나 교체할 것은 없습니다. 데이터베이스가 유출되더라도 해시만 노출되며, 모든 토큰은 관리자 화면에서 취소하거나 다시 발급할 수 있습니다.
사용자가 제공하는 서비스 자격 증명
외부 서비스 자격 증명은 환경 변수에서 읽으며 데이터베이스에는 절대 기록되지 않습니다. 제공자에서 교체하고, 변수를 업데이트한 뒤 다시 배포하세요.
| 서비스 | 변수 |
|---|---|
| Google 로그인 | EMDASH_OAUTH_GOOGLE_CLIENT_ID, EMDASH_OAUTH_GOOGLE_CLIENT_SECRET(또는 접두사 없는 별칭) |
| GitHub 로그인 | EMDASH_OAUTH_GITHUB_CLIENT_ID, EMDASH_OAUTH_GITHUB_CLIENT_SECRET(또는 접두사 없는 별칭) |
| Microsoft 로그인 | EMDASH_OAUTH_MICROSOFT_CLIENT_ID, EMDASH_OAUTH_MICROSOFT_CLIENT_SECRET, EMDASH_OAUTH_MICROSOFT_TENANT_ID(또는 접두사 없는 별칭) |
| Turnstile(댓글) | EMDASH_TURNSTILE_SECRET_KEY(또는 TURNSTILE_SECRET_KEY) |
| S3 호환 스토리지 | S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY, S3_ENDPOINT, S3_BUCKET, S3_REGION |
Cloudflare에서는 wrangler secret put으로 설정하고, 로컬 개발에서는 .env에 넣으세요. Wrangler는 .dev.vars와 .env 중 하나만 읽으며 둘 다 읽지는 않습니다. .dev.vars가 있으면 그쪽이 우선합니다. 바인딩을 통한 R2에는 액세스 키 변수가 필요하지 않습니다. 바인딩이 런타임 접근 권한을 부여하기 때문입니다. 미디어 스토리지를 참고하세요.
Node에서 EmDash는 요청이 도착할 때 서버의 프로세스 환경에서 Turnstile 시크릿 키를 읽으며, astro dev와 빌드된 서버 모두 .env를 그 환경에 로드하지 않습니다. 호스트 환경에 키를 설정하거나, astro dev를 실행하는 셸에서 export하거나, node --env-file=.env ./dist/server/entry.mjs로 빌드된 서버를 시작하세요.
플러그인 비밀
플러그인이 type: "secret"으로 선언하는 설정(이메일 제공자 API 키, 폼 CAPTCHA 등)은 관리자 UI에서 입력하며, options 테이블의 plugin:<id>:settings:<key> 아래에 암호화되어 저장됩니다. 일치하는 암호화 키를 사용할 수 없는 경우에도 관리자 화면은 해당 비밀이 설정되어 있는지 여부만 전달받으므로, 관리자는 읽을 수 없는 자격 증명을 교체할 수 있습니다. 플러그인 코드는 격리된 런타임 안에서 ctx.settings를 통해 평문을 읽습니다. 선언된 설정 스키마 밖에 저장된 값(임의의 플러그인 KV와 상태 항목 포함)은 이 암호화 경로를 사용하지 않습니다.
- 자격 증명 교체: 제공자에서 자격 증명을 교체하고 새 값을 플러그인의 설정 페이지에 붙여 넣으세요. 저장하면 새 암호화 엔벨로프가 기록됩니다.
- 평문 마이그레이션: 이전 EmDash 버전에서 저장된 비밀도 계속 읽을 수 있습니다. 각 값을 다시 저장해 암호화하세요.
- 암호화 키를 잃어버린 경우: 별도로 보관한 키 백업에서 일치하는
EMDASH_ENCRYPTION_KEY를 복원하세요. 사본이 없다면 영향을 받는 모든 자격 증명을 제공자에서 교체하고, 새 암호화 키를 구성한 뒤 교체한 값을 입력하세요.
CLI 자격 증명
emdash CLI는 사이트 토큰을 ~/.config/emdash/auth.json(XDG_CONFIG_HOME을 따름)에 저장하며, 소유자만 접근할 수 있는 모드(0600)로 설정합니다. emdash login은 OAuth 디바이스 플로우로 EmDash 인스턴스에 인증하고, 인스턴스 URL을 키로 토큰을 저장합니다. emdash logout은 이를 제거하며, --token 또는 EMDASH_TOKEN은 호출마다 저장된 토큰을 재정의합니다.
이 파일을 잃어버려도 괜찮습니다. emdash login을 다시 실행하면 됩니다.
플러그인 레지스트리 CLI 자격 증명
독립 실행형 emdash-plugin CLI(패키지 @emdash-cms/plugin-cli)는 실험적인 AT Protocol 레지스트리를 대상으로 합니다. 그곳에 게시하는 것은 사용자의 AT Protocol ID(게시자 DID)에 연결됩니다. 사이트 자체는 게시 자격 증명을 보유하지 않으며, 설치 시 해당 DID에 귀속된 게시 레코드의 체크섬에 대해 아티팩트가 검증됩니다.
- atproto OAuth로 인증합니다. OAuth 세션/상태 데이터는
~/.emdash/oauth/에 있고, 게시자 ID(DID, 핸들, PDS)는~/.emdash/credentials.json에 캐시됩니다. 둘 다 소유자만 접근할 수 있는 모드로 기록됩니다. - CI에서는
EMDASH_PUBLISHER_DID,EMDASH_PUBLISHER_HANDLE,EMDASH_PUBLISHER_PDS로 ID를 제공하세요.EMDASH_REGISTRY_URL은 레지스트리 호스트를 재정의합니다. CI에서publish를 자동화하려면 여전히 러너의~/.emdash/oauth/에 OAuth 세션 파일이 필요합니다. 환경 변수만으로는 OAuth 세션을 전달할 수 없습니다. - 게시 접근 권한의 교체나 취소는 EmDash가 아니라 AT Protocol 계정(예: 앱 비밀번호)에서 이루어집니다. Atmosphere auth를 참고하세요.
교체 빠른 참조
| 하고 싶은 일 | 방법 |
|---|---|
| 플러그인 설정 암호화 교체 | 새 키를 앞에 추가하고, 플러그인 비밀을 다시 저장한 뒤, 이전 키 제거 |
| 모든 미리보기 링크 무효화 | emdash:preview_secret 행을 삭제(또는 환경 변수 재정의를 변경)하고 다시 배포 |
| 댓글 속도 제한 해시 초기화 | EMDASH_IP_SALT를 변경(또는 emdash:ip_salt 옵션 행 삭제)하고 다시 배포 |
| 유출된 API 토큰 취소 | Admin → Users → API tokens → 취소한 뒤 대체 토큰 생성 |
| 모든 세션 종료 | 세션 저장소(Workers KV 네임스페이스 / 세션 디렉터리) 비우기 |
| 제공자 자격 증명 교체 | 제공자에서 교체하고, 환경 변수를 업데이트한 뒤 다시 배포 |
| 플러그인 API 키 교체 | 제공자에서 교체하고, 플러그인의 관리자 설정에서 다시 입력 |