Vault 세션/정책 모델 (3 layer)
AXE Vaultwarden (Timshel fork 기반, D-ops-37 + D-ops-40) 의 session/unlock 정책은 3 layer 누적으로 결정. 각 layer 가 누가 통제하는지 + 변경 비용이 다름.
| Layer | 통제 누가 | 영향 범위 | 변경 비용 |
|---|---|---|---|
| 1. Server env (Vaultwarden config) | 운영자 (compose env_file) | 모든 user, 모든 client | container restart 1회 |
| 2. Org Policies (Bitwarden 호환 정책) | Org Owner (web UI 또는 API) | 해당 org member 전원, 모든 client | 즉시 (next login 적용) |
| 3. Per-client preferences (localStorage) | 각 user 본인 (각 device/client 마다) | 본인의 그 client 만 | 본인 30초 |
3 layer 가 누적 적용 — 각 layer 가 더 paranoia 방향으로 강제하고, 마지막 user 자율 layer 가 ceiling 안에서 편의 조정.
Layer 1 — Server env
Vaultwarden config 의 env var. /Users/axe/.axe/vault/docker-compose.yml 의 environment: 블록.
핵심: SSO_AUTH_ONLY_NOT_SESSION
| 값 | 효과 |
|---|---|
false (default) | SSO 의 refresh token 따라감 — Entra ID 의 token 만료 시점에 자동 logout → re-SSO+MP 강제. user 입력 빈도 ↑ |
true (AXE 현재 설정, 2026-05-26) | SSO 가 인증만, session lifecycle 은 Vaultwarden 자체 30일 idle refresh token 사용. user 가 30일 안 idle 안 하면 무한 갱신 |
적용 효과 (AXE 4 user):
- SSO+MP 재입력 = 약 월 1회 (idle 30일 + browser 재시작 시)
- Touch ID/PIN unlock 은 daily
trade-off:
- Entra 측 user 비활성/제명이 server session 에 ≤30일 lag
- offboarding 시 운영자가 수동으로 vault 측에서도 user 비활성 (UI: AXE org → Manage → Members → user → Remove + admin panel 에서 user disable) — AXE 4명 규모 충분 처리 가능
- customer 규모 ↑ 시 재평가 (별 결정)
다른 관련 env
| env | 효과 |
|---|---|
SSO_ONLY=false | MP 단독 fallback 유지 (D-ops-26 + D-ops-27) |
SIGNUPS_ALLOWED=true | JIT user provisioning (SSO 신규 user 자동 생성) |
SSO_SCOPES="openid profile offline_access User.Read" | offline_access = refresh token 발급 |
ORGANIZATION_INVITE_AUTO_ACCEPT=true | invite Accept 단계 자동 통과 |
Vaultwarden 미지원 (의도):
- ❌ Maximum Vault Timeout — Bitwarden commercial license, AGPLv3 비호환 (source comment :
// MaximumVaultTimeout = 9, // Not supported (Not AGPLv3 Licensed)) - ❌ Disable Personal Vault Export — 동일 license 사유
- ❌ Activate Autofill, Automatic App LogIn — Vaultwarden 미구현
→ vault timeout 의 server-side 강제는 영구 영역 외. SSO_AUTH_ONLY_NOT_SESSION=true 가 가용 영역 안 best 대체.
Layer 2 — Org Policies (Bitwarden 호환)
Org Owner 가 /api/organizations/<orgId>/policies/<type> (Bitwarden 정책 docs ) 또는 web UI 로 설정. Vaultwarden 의 supported set 안에서.
현재 AXE org 적용 (2026-05-26, D-ops-40 Progress)
| type | 정책 | 값 | 효과 |
|---|---|---|---|
| 1 | MasterPassword | min 12자, complexity 1 | MP 변경 시점 검증. 기존 MP 영향 0 (paranoia 옵션) |
| 2 | PasswordGenerator | length 20, upper+lower+number+special | item 신규 생성 시 강한 default |
| 3 | SingleOrg | enabled | member 가 다른 org 가입 차단 → 데이터 누설 방지 |
| 5 | PersonalOwnership | enabled | 신규 item 은 org collection 필수 (D-ops-32 본질 정합) |
의도적 미설정 (D-ops-38 + D-ops-26+27):
| type | 정책 | 왜 안 켜나 |
|---|---|---|
| 0 | TwoFactorAuthentication | Entra SSO 가 이미 MFA 역할 (D-ops-38) — vault 자체 2FA 중복 |
| 8 | ResetPassword | 운영자 직권 reset 보존 (default), 강제 policy 불필요 |
| 14 | RemoveUnlockWithPin | PIN unlock = 일상 편의 핵심, 차단하면 user 경험 악화 |
Vaultwarden 미지원 (Bitwarden Cloud 에서는 가능):
| type | 정책 | 사유 |
|---|---|---|
| 4 | RequireSso | Vaultwarden 미구현 (의도 — MP 단독 fallback 보존 정책과 정합) |
| 9 | MaximumVaultTimeout | AGPL 비호환 |
| 10 | DisablePersonalVaultExport | AGPL 비호환 |
API 호출 패턴
# AXE org policy 조회
export BW_SESSION="$(security find-generic-password -s 'axe.vault.session' -w)"
AI_USER=0ef2361b-5a69-4c23-973d-e978a4d55512 # ai@
ORG=0c5d8bbd-ad85-42b4-8b8a-2849031981b1 # AXE
TOKEN=$(jq -r ".\"user_${AI_USER}_token_accessToken\"" \
"$HOME/Library/Application Support/Bitwarden CLI/data.json")
curl -sf -H "Authorization: Bearer $TOKEN" \
"https://axe.axelabs.ai/vault/api/organizations/$ORG/policies" \
| jq '.data | map({type, enabled})'
# 정책 변경 (예: MasterPassword 정책 수정)
curl -sf -X PUT \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
"https://axe.axelabs.ai/vault/api/organizations/$ORG/policies/1" \
-d '{"type":1,"enabled":true,"data":{"minComplexity":2,"minLength":14,"requireUpper":false,"requireLower":false,"requireNumbers":false,"requireSpecial":false,"enforceOnLogin":false}}'token 만료 시 (~1h) bw sync 로 refresh.
Layer 3 — Per-client preferences
vault timeout / PIN / Touch ID / theme = 각 device 의 client localStorage. server 가 못 건드림 (Bitwarden 디자인 — privacy by design, prefs 가 user device 안). 정책 부재 시 각 client 의 첫 사용에서 user 가 선택.
AXE 표준 setup (KDF rotation 완료 후 권장)
4 user 모두 + 모든 새 직원 다음 3 client 설정:
A. Web vault (보조용, https://axe.axelabs.ai/vault )
| field | 권장 |
|---|---|
| Vault timeout | On browser refresh (web vault 의 최장 옵션 — Never 노출 안 됨) |
| Vault timeout action | Lock (Logout 절대 X — SSO 재진입 비용) |
설정 위치: 우상단 본인 이름 → Account settings → Preferences
B. Chrome extension (일상 brower-bound)
| field | 권장 |
|---|---|
| Server URL (self-hosted) | https://axe.axelabs.ai/vault |
| Vault timeout | Never (extension 은 Never 노출됨) |
| Vault timeout action | Lock |
| Unlock with PIN code | ✅ + PIN 4~12자 (e.g. axe-2026 word) |
| Lock with master password on browser restart | ✅ (paranoia 균형) |
C. Native macOS app (daily driver — 가장 편함)
| field | 권장 |
|---|---|
| Server URL (self-hosted) | https://axe.axelabs.ai/vault |
| Vault timeout | Never |
| Vault timeout action | Lock |
| Unlock with Touch ID | ✅ (macOS Secure Enclave) |
| Lock with master password on browser restart | ✅ |
실 효과 (3 layer 누적):
- daily unlock = Touch ID (수 초)
- 주 1-2회 = PIN (Chrome ext)
- 월 1회 (idle 30일 또는 paranoia 재시작) = MP
Vault scope — 무엇을 보호하는가 (trust boundary)
Vault 는 secret 의 저장 + retrieval + access control 만 담당하는 저장소. vault item 을 delete/revoke 했다고 해서 그 secret 이 의미하는 외부 service 의 권한·계정·2FA 가 자동으로 무효화되지 않음. 두 영역을 혼동하면 offboarding/사고 대응 시 access 잔존 위험.
경계 도식
┌─ Vault scope (이 페이지 + Vaultwarden 이 통제) ──────────┐
│ • secret 값의 저장 / 암호화 (Argon2id + KDF) │
│ • org collection 기반 access control (Layer 2) │
│ • SSO + MP unlock lifecycle (Layer 1, 3) │
│ • item delete/revoke → 그 저장 위치 에서 차단 │
└──────────────────────────────────────────────────────────┘
↓ vault item 은 외부 service 의 "거울" 일 뿐
┌─ 외부 service scope (vault 가 못 건드림) ─────────────────┐
│ • 해당 service 측 계정 자체 (Microsoft / Google / ...) │
│ • TOTP secret 의 generation (해당 service 가 발급) │
│ • OAuth client_secret / refresh token invalidation │
│ • GitHub PAT, API key 의 server 측 revoke │
│ • customer 측 데이터 자체 (KB / 홈택스 export 등) │
└──────────────────────────────────────────────────────────┘→ 회수 범위 = vault item delete + 외부 service 측 절차. 둘 다 해야 완전.
회수 대상별 분리표
| 회수 대상 | vault 가 처리 | 외부 service 측 절차 필요 |
|---|---|---|
| service password 저장값 | ✅ item delete/revoke | 해당 service 의 password reset (admin 또는 본인) |
| TOTP secret (2FA) | ✅ item delete | 해당 service 의 2FA reset → TOTP secret regenerate |
| OAuth client_secret | ✅ item delete | Azure / Google / GitHub 측에서 client_secret 재발급 + 옛값 폐기 |
| GitHub PAT | ✅ item delete | GitHub Settings → Personal access tokens → Revoke (server 측 token row 무효화) |
| API key (Anthropic / OpenAI / etc) | ✅ item delete | 해당 provider 의 console 에서 key revoke |
| KB / 홈택스 / 회계법인 portal admin 계정 | ❌ vault scope 외 (계정 자체는 vault 가 보관 안 함) | 해당 portal admin 의 계정 비활성/삭제 |
| Microsoft 365 / Entra ID 계정 | ❌ vault scope 외 | customer IT 가 Entra ID 에서 user 비활성 |
| customer 측 데이터 (Excel, PDF 등 export 본) | ❌ vault scope 외 | customer 자체 DLP / device wipe / 법적 요구 |
함의
- offboarding 시: /ops/runbook/employee-offboarding 의 “외부 service 권한 회수” 표를 vault item 제거와 반드시 병행.
- secret rotation 시: vault item 값을 새로 채우는 것 만으로는 옛값이 외부 service 측에서 살아있음 — /ops/runbook/secret-rotation 의 외부 service rotation step 동반.
- 사고 대응 시: vault item delete = “이 운영자 머신/AXE org 안에서 더 못 꺼냄”. 그 secret 이 leak 된 정황이면 외부 service 측 revoke 가 본질.
3 layer 누적 그림
┌──────────────────────────────────────────────────────────┐
│ Layer 3: Per-client preferences (user 자율) │
│ • web `On browser refresh` Lock │
│ • extension `Never` + PIN │
│ • native `Never` + Touch ID │
│ ↓ 위 layer 의 ceiling 안에서 user 편의 조정 │
├──────────────────────────────────────────────────────────┤
│ Layer 2: Org Policies (Owner 통제, member 전원 적용) │
│ • MasterPassword min 12, complexity 1 │
│ • PasswordGenerator length 20 │
│ • SingleOrg │
│ • PersonalOwnership │
│ ↓ 모든 member 자동 │
├──────────────────────────────────────────────────────────┤
│ Layer 1: Server env (운영자 통제, 전 instance) │
│ • SSO_AUTH_ONLY_NOT_SESSION=true → 30일 session │
│ • SSO_ONLY=false → MP fallback │
│ • SIGNUPS_ALLOWED=true → JIT │
│ ↓ 인스턴스 기본값 │
└──────────────────────────────────────────────────────────┘새 직원 onboarding 의무 step
- KDF rotation (1회) — B-vault-axe.2-sso-mp-incomplete runbook
- 옛 PBKDF2 → Argon2id 로 vault re-encrypt
- SSO→MP wrapped variant 정합 (D-ops-40)
- Layer 3 setup (1회 per device) — 위 Web/Extension/Native 표 그대로
- 끝 — 이후 일상은 Touch ID/PIN 만
운영자가 1번 안내 1회 + 2번 표 공유 1회 = onboarding 끝. layer 1+2 는 server 가 자동.
Customer (Truvia 등) — sovereignty
| Layer | customer 자율도 |
|---|---|
| Layer 1 (server env) | customer 자체 통제 — customer 의 macmini compose, customer 자율. AXE 가 권장만 (이 페이지 참조 안내) |
| Layer 2 (org policies) | customer org Owner 가 설정 — AXE 가 정책 enforce 불가. 권장 (위 4 정책 동일) |
| Layer 3 (per-client) | customer 직원 본인 — 동일 setup 권장 |
→ customer 운영자에게 본 페이지 link 만 전달, 권장 follow. /services/vault 의 customer onboarding 단계에 vault setup 포함 (TODO).
함정
| 함정 | 결과 | 회피 |
|---|---|---|
| Maximum Vault Timeout 정책 시도 (Bitwarden 패턴) | HTTP 400 “Invalid or unsupported policy type” | Vaultwarden 미지원 (AGPL). 대신 SSO_AUTH_ONLY_NOT_SESSION=true 사용 |
SSO_ONLY=true + Entra ID 장애 | 운영자 vault 진입 불가 (lockout) | SSO_ONLY=false 유지 (D-ops-26 + D-ops-27) — MP fallback 보존 |
| RemoveUnlockWithPin 정책 enable | PIN unlock 불가 → 일상 MP 입력 강제 | 본 정책 enable 금지 (paranoia 와 편의 균형) |
| 옛 KDF (PBKDF2) user 가 SSO→MP unlock 실패 | client 가 wrapped MP 변환 불가 | user 별 KDF rotation 의무 (onboarding step 1) |
| Org policy 변경이 즉시 client 에 반영 안 되는 듯 | client 가 sync 시점에 fetch | bw sync 또는 client 강제 sync (일반적 ≤1분 안 자동) |
| 운영자가 Layer 3 강제 시도 (Chrome managed policy 등) | macmini 별 enrollment 필요 — overhead 큼 | AXE 4명 규모 = docs 안내 + 신뢰. 10+ 직원 시점에 재평가 (B-vault-chrome-mdm-policy-distribution) |
| Entra user 비활성 후 ≤30일 server session 잔존 | offboard 직후 vault 접근 가능 | offboard 시 운영자가 vault admin panel 에서 user disable 같이 실행 (manual checklist) |
관련
- D-ops-37 — AXE-local Vaultwarden fork build
- D-ops-40 — axe.3 release plan + Progress (본 정책 결정)
- D-ops-38 — 외부 service 2FA = vault TOTP, 내부 2FA skip
- D-ops-26 + D-ops-27 — SSO_ONLY=false + MP fallback
- /architecture/auth — Entra ID + OAuth-RP middleware
- /architecture/secrets — 서비스 secret 관리 (vault item)
- B-vault-axe.2-sso-mp-incomplete — KDF rotation runbook
- B-vault-collection-migration-v1 — personal vault → org collection 정리