Skip to content

Commit 6baad39

Browse files
committed
docs(design): tighten auth/session/csrf/role-config per review
Addresses round-5 review feedback: - CodeRabbit (MD040): add `text` language tag to ASCII-diagram and login-flow fenced code blocks so markdownlint passes. - CodeRabbit (minor): list POST /admin/api/v1/auth/login and /auth/logout in the Section 4.1 endpoint contract table so the admin API shape stays complete. - CodeRabbit (major) + codex P2: remove the localStorage CSRF scheme (XSS-exposed) and adopt double-submit cookie with Secure + SameSite=Strict; pair with a same-origin session cookie that is always HttpOnly + Secure + SameSite=Strict regardless of TLS mode. - codex P2: make overlap between admin.read_only_access_keys and admin.full_access_keys a hard startup failure rather than an implementation-order-dependent silent fallback.
1 parent 840f3c5 commit 6baad39

1 file changed

Lines changed: 17 additions & 5 deletions

File tree

docs/design/2026_04_24_proposed_admin_dashboard.md

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ elastickv は現在、DynamoDB 互換 (`adapter/dynamodb.go`) と S3 互換 (`ad
4747

4848
## 3. Architecture Overview
4949

50-
```
50+
```text
5151
┌─────────────────────────────────────────────────────────────┐
5252
│ elastickv node │
5353
│ │
@@ -144,6 +144,8 @@ Admin API は internal な JSON API であり、AWS API プロトコルには準
144144

145145
| Method | Path | 概要 |
146146
|---|---|---|
147+
| `POST` | `/admin/api/v1/auth/login` | access key / secret key で認証しセッション Cookie を発行 (Section 6.1) |
148+
| `POST` | `/admin/api/v1/auth/logout` | セッション Cookie を失効させる |
147149
| `GET` | `/admin/api/v1/cluster` | ノード ID, Raft リーダー, バージョンを返す |
148150
| `GET` | `/admin/api/v1/dynamo/tables` | テーブル一覧 (`ListTables` を内部で呼ぶ) |
149151
| `POST` | `/admin/api/v1/dynamo/tables` | テーブル作成 (`CreateTable` 相当) |
@@ -250,15 +252,19 @@ SPA フォールバックは **1〜3 にマッチしなかった場合のみ**
250252
- 既存の SigV4 資格情報テーブル (config に静的定義) を再利用する。
251253
- 管理 UI は **ブラウザに SigV4 署名ロジックを実装しない**。代わりに以下のフローを採る。
252254

253-
```
255+
```text
254256
1. POST /admin/api/v1/auth/login
255257
body: { "access_key": "...", "secret_key": "..." }
256258
server: signs a short-lived (1h) JWT with HS256 using a server-side
257-
admin signing key. Returns JWT in an httpOnly cookie.
259+
admin signing key. Returns JWT in a session cookie.
258260
259261
2. 以降の /admin/api/v1/* は Cookie で認証。
260262
```
261263

264+
- **セッション Cookie 属性 (必須)**: `HttpOnly` / `Secure` / `SameSite=Strict` / `Path=/admin` / `Max-Age=3600` を必ず付与する。
265+
- `Secure` は TLS が有効な時だけでなく **常に付与** する。loopback 平文モードでも同じく付与し、ブラウザが HTTP で送信することを拒否する (ローカル開発は `admin.listen``127.0.0.1` で HTTPS を推奨、どうしても平文が必要なら専用の `--admin-dev-insecure-cookie` フラグで明示的に opt-in)。
266+
- `SameSite=Strict` によりクロスサイト書き込み攻撃を阻止。
267+
- 仕様を満たさない Cookie を出す実装はユニットテストで検出する (Set-Cookie ヘッダを正規表現で検証)。
262268
- 秘密鍵はサーバー側でのみ検証し、以降のリクエストに渡らない。
263269
- **JWT 署名鍵はクラスタ共有**。ノード毎にランダム生成する案は、ロードバランサの背後で複数ノードに分散された時にノード A で発行したトークンがノード B で検証失敗する (断続的なログアウトが発生する) ため却下。
264270
- 採用: 起動時に設定ファイル `admin.session_signing_key` (64 byte の base64、Kubernetes Secret 等で配布) を読み込み、全ノードで同一の HS256 鍵を使う。
@@ -281,11 +287,17 @@ full_access_keys = ["AKIA_ADMIN_1"]
281287
- `read_only_access_keys` でログインしたセッションは `GET` のみ許可。
282288
- `full_access_keys` はすべての CRUD を許可。
283289
- どちらにも含まれない access key でのログインは 403。
290+
- **ロール重複の起動時検証**: 同じ access key が `read_only_access_keys``full_access_keys` の両方に含まれた場合、実装の lookup 順序次第で read-only のつもりの鍵に書き込み権限が与わる事故を招くため、**設定読み込み時に集合交差を検出したらハードエラーで起動失敗する**。黙って片方を優先する fallback は置かない。ユニットテストで「重複した設定 → 起動失敗」を必ず固定する。
284291

285292
### 6.3 CSRF
286293

287-
- 変更系 (`POST`/`PUT`/`DELETE`) は `X-Admin-CSRF` ヘッダを要求する。
288-
- ログインレスポンスで `csrf_token` をボディで返し、SPA が `localStorage` に保管、全書き込みリクエストに付与する。
294+
変更系 (`POST`/`PUT`/`DELETE`) は **double-submit cookie パターン**で CSRF を防ぐ。`localStorage` にトークンを置く案は XSS 時に奪取されるため却下。
295+
296+
- ログインレスポンスで `csrf_token`**別の Cookie** (`admin_csrf`) に `Secure``SameSite=Strict``Path=/admin`**`HttpOnly` なし** で発行する (SPA の JS が読み取る必要があるため、セッション Cookie とは別 Cookie)。
297+
- SPA は書き込みリクエストで `admin_csrf` Cookie の値を読み取り、`X-Admin-CSRF` ヘッダに入れて送る。
298+
- サーバは Cookie 値とヘッダ値の一致を検証。ミスマッチなら 403。
299+
- CSRF Cookie はセッション Cookie と同じ有効期限 (1h) で回転。
300+
- `SameSite=Strict` セッション Cookie 単独でもクロスサイト書き込みは大半防げるが、browser bug / ユーザー拡張機能等の防御深度として double-submit を併用する。
289301

290302
---
291303

0 commit comments

Comments
 (0)