Files
netbird-selfhosted/groups-and-policies.md
timmy e22ab219a5 docs: record actual groups/policies rollout + lesson learned
Applied the segmentation plan. Added an 'execution log' section with
real IDs, verification results, and a rollback snippet.

Key lesson corrected in the main plan: the original design missed
personal<->personal connectivity (Mac<->iPhone would have broken
when Default was disabled). Added personal-internal policy and a
note that every desired group pair — including a group with itself
— needs an explicit policy, because NetBird defaults to deny when
there is no matching policy.
2026-04-18 16:18:47 +08:00

339 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Groups 與 Access Control Policies
NetBird 的存取控管基本單位。本文用目前部署的 4 個 peermbp-13-pro、iphone-15-pro、CT100、CT101為例說明 Groups 怎麼用。
## Groups 是什麼
**Group** 是 peer 的具名集合。NetBird 不直接管「peer A 能不能連 peer B」而是
1. 把 peer 丟進 group
2.**Policy** 定義「group X → group Y 的流量允許/禁止」
這讓存取控管的規模隨服務/角色擴展,而不是隨 peer 數量爆炸。
## Groups 在 NetBird 裡出現的 4 個地方
| 地方 | 用途 |
|------|------|
| **Access Control → Policies** | 決定 group A 能不能連 group B、哪些 port/protocol |
| **Setup Keys 的 `auto_groups`** | 用此 key 註冊的 peer 自動進指定 group |
| **Network Routes** | 哪個 group 能透過某個 peer 走一條路由(例:透過 CT100 當 exit node |
| **DNS / Nameservers** | 哪個 group 套用特定 DNS 設定 |
這四個功能都以 group 當接口,換句話說 — **group 設計對了,整個存取模型就清楚了**
## 預設:`All` group 與 `Default` policy
NetBird 初始化時自動建立:
- **`All`** group — 所有 peer 自動加入
- **`Default`** policy — `All → All`allow all protocols
所以剛部署完,所有 peer 彼此互通(就像你目前的狀態)。這對起步階段方便,但**不符合最小權限原則**。
Dashboard 的 Peers 頁面顯示 `Add Groups` 按鈕代表該 peer **沒有自訂 group**(它仍然自動在 `All` 裡)。
## 何時要自訂 Group
### 典型情境
1. **角色分離**:個人裝置 vs 伺服器,不希望伺服器能主動戳個人裝置
2. **最小權限**iPhone 只需能連特定服務,不需連所有 peer
3. **自動化註冊**:用 setup key 部署新機時自動分類(`ci-runners``edge-nodes` 等)
4. **分享給家人**:一個 group 只給家人裝置policy 限制他們能看到的範圍
5. **Exit Node 授權**:只有特定 group 能把流量導去某 peer
### 本部署的分組(已套用,詳見文末執行紀錄)
4 個 peer 分成 2 個自訂 group
| Group | 成員 | 用途 |
|-------|------|------|
| `servers` | CT100、CT101 | 家裡 headless 服務 |
| `personal` | mbp-13-pro、iphone-15-pro | Timmy 的個人裝置 |
對應的 Policy
| # | From | To | Bidirectional | 目的 |
|---|------|-----|--------------|------|
| 1 | `personal` | `servers` | 否 | 個人裝置能連伺服器 |
| 2 | `servers` | `servers` | 是 | 伺服器彼此互連(例如未來備份、日誌集中) |
| 3 | `personal` | `personal` | 是 | Mac ↔ iPhone 互通(**不能漏**,見下方教訓) |
| 4 | ~~`All → All` (Default)~~ | — | — | **停用**(保留可快速 rollback |
⚠️ **教訓**`personal-internal` policy 是必需的。原本只設 `personal → servers``servers → servers` 會切斷 Mac ↔ iPhone — 每個想互通的 group 組合都要一條明確 policy包括 group 自己到自己。
刻意**不**建立 `servers → personal` 的 policy伺服器被入侵時攻擊者無法透過 NetBird 橫向移動到個人裝置。
(未來如果有多個使用者,可再拆 `timmy-devices``family-devices` 等。)
## Peer 可以同時屬於多個 group
常見用法:
- `CT100`: in both `servers` and `jump-boxes`(對不同 policy 都有效)
- `CT101`: in both `servers` and `rustdesk`(另一條政策只開 rustdesk port 給某個子集)
Peer group 是集合運算policy 匹配時用 ORpeer 只要在 policy 的 source group 任一個裡就算命中。
## 怎麼建 Group
### 方式 AUI推薦起步
1. Dashboard → **Access Control → Groups****Create Group**
2.**Name**(例 `servers`
3.**Peers** 勾選要加入的 peer
4. **Create**
建完後去 **Access Control → Policies****Peers** 頁面都能再綁定 group。
### 方式 BAPI
```bash
TOKEN=<你的 PAT>
# 先查目前 peer 清單拿 ID
curl -sf -H "Authorization: Token $TOKEN" \
https://netbird.timmy.us.kg/api/peers | jq '.[] | {id, name}'
# 建 servers group同時塞兩個 peer
curl -sf -X POST -H "Authorization: Token $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "servers",
"peers": ["<CT100-peer-id>", "<CT101-peer-id>"]
}' \
https://netbird.timmy.us.kg/api/groups | jq
# 建 personal group
curl -sf -X POST -H "Authorization: Token $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "personal",
"peers": ["<Mac-peer-id>", "<iPhone-peer-id>"]
}' \
https://netbird.timmy.us.kg/api/groups | jq
```
## 怎麼建 Policy
### 方式 AUI
1. **Access Control → Policies****Add Policy**
2. **Name**:例 `personal-to-servers`
3. **Source Groups**:選 `personal`
4. **Destination Groups**:選 `servers`
5. **Protocol**`All`(或 TCP/UDP/ICMP
6. **Ports**:留空代表全部
7. **Bidirectional**:看需求
- 勾了 → 來回都允許(大多數家用場景這樣方便)
- 不勾 → 只允許 source 主動連 destination
8. **Create**
### 方式 BAPI
```bash
curl -sf -X POST -H "Authorization: Token $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "personal-to-servers",
"enabled": true,
"description": "Personal devices can access servers",
"rules": [{
"name": "personal-to-servers",
"enabled": true,
"action": "accept",
"protocol": "all",
"sources": ["<personal-group-id>"],
"destinations": ["<servers-group-id>"],
"bidirectional": false
}]
}' \
https://netbird.timmy.us.kg/api/policies | jq
```
## 刪除預設 `Default` policy
當你建立好新的 `personal-to-servers``servers-to-servers` 等 policy 後,才關掉預設的 `All → All`,不然會暫時中斷連線。
UI**Access Control → Policies → Default** → 右邊三點 → **Delete** 或 toggle **Disabled**
API
```bash
# 先找 policy ID
curl -sf -H "Authorization: Token $TOKEN" \
https://netbird.timmy.us.kg/api/policies | jq '.[] | {id, name, enabled}'
# 停用(較安全,出問題可立刻打開)
curl -sf -X PUT -H "Authorization: Token $TOKEN" \
-H "Content-Type: application/json" \
-d '<GET 出來的內容,改 "enabled": false>' \
https://netbird.timmy.us.kg/api/policies/<POLICY_ID>
# 或直接刪除
curl -X DELETE -H "Authorization: Token $TOKEN" \
https://netbird.timmy.us.kg/api/policies/<POLICY_ID>
```
## Setup Key 搭配 `auto_groups`
部署新 peer 時可以**一步到位自動加 group**
```bash
# 建一把 key註冊進來的 peer 自動進 servers group
curl -sf -X POST -H "Authorization: Token $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "CT102-whatever",
"type": "one-off",
"expires_in": 86400,
"usage_limit": 1,
"ephemeral": false,
"auto_groups": ["<servers-group-id>"]
}' \
https://netbird.timmy.us.kg/api/setup-keys | jq
```
如此 CT100/CT101 這類部署可以省掉「註冊完再手動加 group」的步驟。
(之前 CT100/CT101 的 setup key 我建的時候 `auto_groups` 是空的 — 未來部署時記得帶上)
## Policy 細節補充
### Port / Protocol 限制
```yaml
# 只允許 SSH (TCP/22) 從 personal 連到 servers
rules:
- protocol: tcp
ports: ["22"]
sources: [<personal-group-id>]
destinations: [<servers-group-id>]
```
### Bidirectional 的真正含義
- `bidirectional: true` — A → B 與 B → A 都允許
- `bidirectional: false` — 只允許 source 端主動 initiate但回應封包 NetBird 會自動放行stateful不用特別設
家用場景建議**預設 `false`**,只在真的需要雙向 initiate 時才開。
### Posture Checks進階
Policy 可綁 Posture Check — 要求 peer 滿足某些條件OS 版本、地理位置、已登入 SSO 等)才能走這條 policy。進階情境可參考 NetBird docs。
## 驗證 Policy 生效
### 方式 A從 peer 端測試
```bash
# 從 Mac ping iPhone (現在可以) → 建立 personal-only policy 後,應該還是可以
ping 100.71.63.111
# 從 CT100 ping Mac (現在可以) → 刪掉 All→All 後,應該不通
ssh root@192.168.42.100 'docker exec netbird netbird status --detail | grep mbp-13-pro -A 3'
```
### 方式 BDashboard → Control Center
視覺化顯示每個 peer 的 policy 連線關係,可以一眼看出誰能連誰。
## 整套搬遷步驟建議
**漸進式,不中斷當前連線**
```
Phase 1: 建 groups
- Create group "servers" with CT100, CT101
- Create group "personal" with Mac, iPhone
Phase 2: 建新 policies先不刪預設
- Policy "personal-to-servers": personal → servers (unidirectional)
- Policy "servers-internal": servers → servers (bidirectional)
- Policy "personal-internal": personal → personal (bidirectional)
- 此時 All→All 跟新 policy 並存,功能不變
Phase 3: 驗證新 policy 生效
- 關掉 All→All先 disable 不 delete
- 實測Mac→CT100 通、CT100→Mac **不通**、Mac↔iPhone 通
Phase 4: 確認無誤
- 刪除 Default policy如果想
- 日後新 peer 記得 setup key 的 auto_groups 要填對
出問題 rollback把 Default policy 重新啟用即可恢復原狀。
```
## 執行紀錄2026-04-18 套用到本部署
實際在本部署上執行此遷移的結果,供日後回溯。
### Groups
| Name | ID | Peers |
|------|-----|------|
| `All` | `d7h3kf6fs58s73f6qbj0` | 4系統預設 |
| `servers` | `d7hjr5fvjtnc738j3reg` | CT100, CT101 |
| `personal` | `d7hjr5fvjtnc738j3rfg` | mbp-13-pro, iphone-15-pro |
### Policies
| Name | ID | Source → Destination | Bidirectional | Enabled |
|------|-----|--------------------|--------------|--------|
| `personal-to-servers` | `d7hjr8fvjtnc738j3rh0` | personal → servers | false | ✓ |
| `servers-internal` | `d7hjr8fvjtnc738j3ri0` | servers → servers | true | ✓ |
| `personal-internal` | `d7hjrenvjtnc738j3rj0` | personal → personal | true | ✓ |
| `Default` | `d7h3kf6fs58s73f6qbjg` | All → All | true | **false**(停用,保留) |
### 計畫外的發現:`personal-internal` 是必需的
最初設計只列了兩條新 policy`personal → servers``servers → servers`),停掉 Default 之後**會切斷 Mac ↔ iPhone**。補上 `personal-internal` 才維持個人裝置互通。
**規則**:每個想互通的 group 組合都需要明確 policy**沒有則拒絕**。包括 group 自己到自己。
### 驗證結果
| 連線 | Policy | 結果 |
|------|--------|------|
| Mac → CT100 | personal-to-servers | 通 (32ms) |
| Mac → iPhone | personal-internal | 通 (8ms) |
| CT100 → CT101 | servers-internal | 通 (<1ms, LAN 直連) |
| CT100 → Mac | ✗(無 policy | 100% loss |
| CT101 → iPhone | ✗(無 policy | 100% loss |
產生的安全邊界:伺服器被攻破時無法透過 NetBird 橫向連到個人裝置。
### Rollback 方式
如果需要暫時恢復全互通:
```bash
TOKEN=<your PAT>
DEFAULT_PID="d7h3kf6fs58s73f6qbjg"
ALL_GID="d7h3kf6fs58s73f6qbj0"
curl -X PUT -H "Authorization: Token $TOKEN" -H "Content-Type: application/json" \
-d "{
\"name\":\"Default\",
\"description\":\"This is a default rule that allows connections between all the resources\",
\"enabled\":true,
\"rules\":[{
\"name\":\"Default\",
\"enabled\":true,
\"action\":\"accept\",
\"protocol\":\"all\",
\"bidirectional\":true,
\"sources\":[\"$ALL_GID\"],
\"destinations\":[\"$ALL_GID\"]
}]
}" \
https://netbird.timmy.us.kg/api/policies/$DEFAULT_PID
```
## 相關檔案與文件
- `setup-keys-and-pat.md` — PAT 與 setup key 建立(建 group 需要 PAT
- `client-troubleshooting.md` — 如果改 policy 後 peer 突然連不到,先查這裡
- `peer-deployment-ops.md` — 新部署 peer 時的 setup key + auto_groups 用法