# AWOOOI RBAC 權限架構設計

> **版本**: v1.0
> **建立日期**: 2026-03-20
> **負責人**: CISO
> **狀態**: Phase 0 草稿

---

## 概述

本文件定義 AWOOOI 的角色基礎存取控制 (RBAC) 架構，採用簡化設計原則，解決舊系統「8+ 角色難以理解」的痛點。

---

## 設計原則

1. **簡單直觀**: 4 個核心角色，一目瞭然
2. **資源導向**: 角色 + 資源級權限，彈性組合
3. **最小權限**: 預設拒絕，明確授權
4. **可審計**: 所有權限變更留有記錄

---

## 角色定義

### 四大核心角色

| 角色 | 代碼 | 說明 | 典型用戶 |
|------|------|------|---------|
| **Owner** | `owner` | 組織擁有者，最高權限 | CEO, 創辦人 |
| **Admin** | `admin` | 管理員，可管理用戶與設定 | CTO, CIO, 部門主管 |
| **Member** | `member` | 成員，可操作與查看 | 工程師, 運維人員 |
| **Viewer** | `viewer` | 觀察者，僅可查看 | 稽核人員, 實習生 |

### 角色繼承

```
Owner
  └── Admin
        └── Member
              └── Viewer
```

- 上層角色繼承下層所有權限
- 權限向下傳遞，不可跨層授權

---

## 權限矩陣

### 系統級權限

| 權限 | Owner | Admin | Member | Viewer |
|------|:-----:|:-----:|:------:|:------:|
| 管理組織設定 | ✅ | ❌ | ❌ | ❌ |
| 管理帳單 | ✅ | ❌ | ❌ | ❌ |
| 邀請/移除用戶 | ✅ | ✅ | ❌ | ❌ |
| 變更用戶角色 | ✅ | ✅¹ | ❌ | ❌ |
| 管理 API Keys | ✅ | ✅ | ❌ | ❌ |
| 查看審計日誌 | ✅ | ✅ | ❌ | ❌ |

> ¹ Admin 只能指派 Member/Viewer，不可指派 Admin/Owner

### 模組級權限

| 模組 | 動作 | Owner | Admin | Member | Viewer |
|------|------|:-----:|:-----:|:------:|:------:|
| **Monitor** | 查看儀表板 | ✅ | ✅ | ✅ | ✅ |
| | 配置告警規則 | ✅ | ✅ | ✅ | ❌ |
| | 確認告警 | ✅ | ✅ | ✅ | ❌ |
| **Deploy** | 查看部署 | ✅ | ✅ | ✅ | ✅ |
| | 執行部署 | ✅ | ✅ | ✅² | ❌ |
| | 回滾 | ✅ | ✅ | ✅² | ❌ |
| | 批准部署 | ✅ | ✅ | ❌ | ❌ |
| **Security** | 查看漏洞 | ✅ | ✅ | ✅ | ✅ |
| | 執行掃描 | ✅ | ✅ | ✅ | ❌ |
| | 標記已修復 | ✅ | ✅ | ✅ | ❌ |
| **Tickets** | 查看工單 | ✅ | ✅ | ✅ | ✅ |
| | 建立工單 | ✅ | ✅ | ✅ | ❌ |
| | 關閉工單 | ✅ | ✅ | ✅³ | ❌ |
| **Settings** | 查看設定 | ✅ | ✅ | ✅ | ❌ |
| | 修改設定 | ✅ | ✅ | ❌ | ❌ |
| **AI Copilot** | 使用 AI | ✅ | ✅ | ✅ | ✅ |
| | 執行 AI 建議 | ✅ | ✅ | ✅² | ❌ |

> ² 需資源級權限
> ³ 僅能關閉自己建立的工單

---

## 資源級權限

### 設計理念

角色定義「能做什麼」，資源權限定義「能對什麼做」。

```
最終權限 = 角色權限 ∩ 資源權限
```

### 資源類型

| 資源類型 | 代碼 | 說明 |
|---------|------|------|
| 主機 | `host` | 單一主機 |
| 主機群組 | `host_group` | 主機群組 |
| 服務 | `service` | 單一服務 |
| Pipeline | `pipeline` | 部署流水線 |
| 專案 | `project` | 整個專案 |

### 授權範例

```json
{
  "user_id": "u-001",
  "role": "member",
  "resource_permissions": [
    {
      "resource_type": "host_group",
      "resource_id": "hg-web-servers",
      "actions": ["deploy", "rollback", "restart"]
    },
    {
      "resource_type": "host_group",
      "resource_id": "hg-database",
      "actions": ["view"]  // 只能看，不能操作
    },
    {
      "resource_type": "pipeline",
      "resource_id": "pl-api-deploy",
      "actions": ["execute", "view"]
    }
  ]
}
```

### 資源權限動作

| 資源類型 | 可用動作 |
|---------|---------|
| host / host_group | `view`, `deploy`, `rollback`, `restart`, `ssh`, `manage` |
| service | `view`, `start`, `stop`, `restart`, `scale`, `manage` |
| pipeline | `view`, `execute`, `approve`, `manage` |
| project | `view`, `edit`, `delete`, `manage` |

---

## Multi-Sig 簽核機制

### 適用場景

| 場景 | 風險等級 | 簽核要求 |
|------|---------|---------|
| 生產環境部署 | High | 2 人簽核 (含 1 Admin+) |
| 資料庫 Schema 變更 | Critical | 2 人簽核 (含 CTO) |
| 安全設定變更 | High | 2 人簽核 (含 CISO) |
| 用戶權限提升 | Medium | 1 人簽核 (Admin+) |
| 回滾操作 | Medium | 1 人簽核 (Admin+) |

### 簽核規則

```typescript
interface ApprovalPolicy {
  action: string;
  blastRadius: 'low' | 'medium' | 'high' | 'critical';
  requiredSignatures: number;
  requiredRoles: Role[];
  timeoutMinutes: number;
  escalationPolicy?: EscalationPolicy;
}

const policies: ApprovalPolicy[] = [
  {
    action: 'deploy:production',
    blastRadius: 'high',
    requiredSignatures: 2,
    requiredRoles: ['admin', 'owner'],
    timeoutMinutes: 60,
    escalationPolicy: {
      afterMinutes: 30,
      notifyRoles: ['owner'],
    },
  },
  {
    action: 'security:config_change',
    blastRadius: 'high',
    requiredSignatures: 2,
    requiredRoles: ['admin'], // 至少一人必須有 CISO tag
    timeoutMinutes: 120,
  },
];
```

### 簽核流程

```
┌─────────────┐    ┌─────────────┐    ┌─────────────┐
│  請求發起   │ ─→ │  等待簽核   │ ─→ │  執行操作   │
│  (Member)   │    │  (Admin+)   │    │  (系統)     │
└─────────────┘    └─────────────┘    └─────────────┘
                          │
                          ▼
                   ┌─────────────┐
                   │  超時處理   │
                   │  (自動拒絕) │
                   └─────────────┘
```

---

## 資料模型

### 資料庫 Schema

```sql
-- 用戶表
CREATE TABLE users (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    email VARCHAR(255) NOT NULL UNIQUE,
    name VARCHAR(255) NOT NULL,
    role VARCHAR(50) NOT NULL DEFAULT 'viewer',
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    last_login_at TIMESTAMP WITH TIME ZONE,
    is_active BOOLEAN DEFAULT TRUE,

    CONSTRAINT valid_role CHECK (role IN ('owner', 'admin', 'member', 'viewer'))
);

-- 資源權限表
CREATE TABLE resource_permissions (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    resource_type VARCHAR(50) NOT NULL,
    resource_id VARCHAR(255) NOT NULL,
    actions TEXT[] NOT NULL,
    granted_by UUID REFERENCES users(id),
    granted_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    expires_at TIMESTAMP WITH TIME ZONE,

    UNIQUE (user_id, resource_type, resource_id)
);

-- 簽核請求表
CREATE TABLE approval_requests (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    action VARCHAR(100) NOT NULL,
    requester_id UUID NOT NULL REFERENCES users(id),
    target_resource_type VARCHAR(50) NOT NULL,
    target_resource_id VARCHAR(255) NOT NULL,
    blast_radius VARCHAR(20) NOT NULL,
    required_signatures INT NOT NULL,
    status VARCHAR(20) NOT NULL DEFAULT 'pending',
    metadata JSONB,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    expires_at TIMESTAMP WITH TIME ZONE NOT NULL,
    completed_at TIMESTAMP WITH TIME ZONE,

    CONSTRAINT valid_status CHECK (status IN ('pending', 'approved', 'rejected', 'expired', 'cancelled'))
);

-- 簽核記錄表
CREATE TABLE approval_signatures (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    request_id UUID NOT NULL REFERENCES approval_requests(id) ON DELETE CASCADE,
    signer_id UUID NOT NULL REFERENCES users(id),
    decision VARCHAR(20) NOT NULL,
    comment TEXT,
    signed_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),

    UNIQUE (request_id, signer_id),
    CONSTRAINT valid_decision CHECK (decision IN ('approve', 'reject'))
);

-- 審計日誌表
CREATE TABLE audit_logs (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    user_id UUID REFERENCES users(id),
    action VARCHAR(100) NOT NULL,
    resource_type VARCHAR(50),
    resource_id VARCHAR(255),
    old_value JSONB,
    new_value JSONB,
    ip_address INET,
    user_agent TEXT,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- 索引
CREATE INDEX idx_resource_permissions_user ON resource_permissions(user_id);
CREATE INDEX idx_resource_permissions_resource ON resource_permissions(resource_type, resource_id);
CREATE INDEX idx_approval_requests_status ON approval_requests(status, expires_at);
CREATE INDEX idx_audit_logs_user ON audit_logs(user_id, created_at);
CREATE INDEX idx_audit_logs_resource ON audit_logs(resource_type, resource_id, created_at);
```

---

## API 設計

### 權限檢查 API

```typescript
// POST /v1/auth/check-permission
interface CheckPermissionRequest {
  userId: string;
  action: string;
  resourceType: string;
  resourceId: string;
}

interface CheckPermissionResponse {
  allowed: boolean;
  reason?: string;
  requiresApproval?: boolean;
  approvalPolicy?: ApprovalPolicy;
}
```

### 簽核 API

```typescript
// POST /v1/approvals
interface CreateApprovalRequest {
  action: string;
  targetResourceType: string;
  targetResourceId: string;
  metadata?: Record<string, any>;
}

// POST /v1/approvals/{id}/sign
interface SignApprovalRequest {
  decision: 'approve' | 'reject';
  comment?: string;
}
```

### 用戶權限 API

```typescript
// GET /v1/users/{id}/permissions
interface UserPermissionsResponse {
  role: Role;
  systemPermissions: string[];
  resourcePermissions: ResourcePermission[];
}

// PUT /v1/users/{id}/role
interface UpdateRoleRequest {
  role: Role;
}

// POST /v1/users/{id}/resource-permissions
interface GrantResourcePermissionRequest {
  resourceType: string;
  resourceId: string;
  actions: string[];
  expiresAt?: string;
}
```

---

## 安全考量

### Token 管理

```typescript
interface JWTPayload {
  sub: string;        // user_id
  role: Role;
  permissions: string[];  // 快取常用權限
  iat: number;
  exp: number;
}

// Token 過期策略
const TOKEN_CONFIG = {
  accessTokenTTL: '15m',   // 存取 Token
  refreshTokenTTL: '7d',   // 刷新 Token
  rotateRefreshToken: true, // 刷新時輪換
};
```

### 敏感操作保護

1. **再次驗證**: 變更權限前要求重新輸入密碼
2. **操作冷卻**: 連續拒絕 3 次後鎖定 15 分鐘
3. **異常偵測**: 異地登入、異常時間操作告警
4. **Session 管理**: 支援踢出其他 Session

### 審計要求

所有以下操作必須記錄審計日誌：

- 用戶登入/登出
- 角色變更
- 資源權限授予/撤銷
- 簽核操作
- 敏感資源存取
- 設定變更

---

## 遷移策略

### 舊系統角色對應

| 舊角色 | 新角色 | 補充權限 |
|-------|-------|---------|
| Super Admin | Owner | - |
| Admin | Admin | - |
| Manager | Admin | 資源級限制 |
| Developer | Member | 開發資源權限 |
| Operator | Member | 運維資源權限 |
| Auditor | Viewer | 審計日誌存取 |
| Guest | Viewer | - |
| API User | Member | API 專用權限 |

### 遷移腳本

```python
# scripts/migrate_roles.py

ROLE_MAPPING = {
    'super_admin': 'owner',
    'admin': 'admin',
    'manager': 'admin',
    'developer': 'member',
    'operator': 'member',
    'auditor': 'viewer',
    'guest': 'viewer',
    'api_user': 'member',
}

def migrate_user_role(old_user):
    new_role = ROLE_MAPPING.get(old_user.role, 'viewer')

    # 特殊處理: manager 需要資源級權限
    if old_user.role == 'manager':
        grant_resource_permissions(
            user_id=old_user.id,
            resource_type='project',
            resource_id=old_user.managed_project,
            actions=['view', 'edit', 'manage']
        )

    return new_role
```

---

## 變更記錄

| 日期 | 版本 | 變更 | 作者 |
|------|------|------|------|
| 2026-03-20 | v1.0 | 初版建立 | CISO |

---

*此文件由 CISO 維護，所有權限相關開發必須遵守此架構。*
