diff --git a/README.md b/README.md index 383b687..7bade0f 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ | `docker-compose-note.md` | Docker Compose 服務架構筆記 | | `ssl_cert_note.md` | SSL 憑證資訊與待處理事項 | | `ssl_deploy_guide.md` | SSL 憑證部署 SOP | +| `admin_access_note.md` | `/admin/` IP 白名單機制(.htaccess + X-Forwarded-For) | | `ssl_certs/` | SSL 憑證檔案(.cer、中繼憑證) | | `reset_npm_password.sh` | NPM 管理員密碼重設腳本 | | `check_ssl.sh` | 憑證狀態檢查腳本(主機名匹配 + 到期天數) | diff --git a/admin_access_note.md b/admin_access_note.md new file mode 100644 index 0000000..a89fb00 --- /dev/null +++ b/admin_access_note.md @@ -0,0 +1,93 @@ +# 管理區存取限制(/admin/ IP 白名單) + +`https://tpesupporter.tpech.gov.tw/admin/` 透過 Apache `.htaccess` 做 IP 白名單,不在白名單的 client 一律 302 導去 `/404`。 + +## 檔案位置 + +| 路徑 | 說明 | +|---|---| +| `hospital_web:/opt/php/html/admin/.htaccess` | 實際生效檔案(容器內為 `/var/www/html/admin/.htaccess`) | +| `hospital_web:/opt/php/html/admin/.htaccess.bak.YYYYMMDD` | 修改前備份命名慣例 | + +> `php/` 子目錄是另一個獨立 git repo,本 repo 不持有 .htaccess 原始檔;改檔請直接在遠端編輯。 + +## 目前規則 + +```apache +RewriteEngine On + +RewriteCond %{HTTP:X-Forwarded-For} !(^|,\s?)61\.220\.124\.10[56]$ +RewriteCond %{HTTP:X-Forwarded-For} !(^|,\s?)220\.135\.27\.129$ +RewriteCond %{HTTP:X-Forwarded-For} !(^|,\s?)223\.137\.237\.10$ +RewriteCond %{HTTP:X-Forwarded-For} !(^|,\s?)10\.209\.(159|160|161)\.[0-9]+$ +RewriteRule .* https://tpesupporter.tpech.gov.tw/404 [R,L] +``` + +| IP / 網段 | 用途 | +|---|---| +| 61.220.124.105 / 106 | 辦公室出口 | +| 220.135.27.129 | jason_hsieh_home_srv 跳板機 | +| 223.137.237.10 | 備援辦公點 | +| 10.209.159–161.x | 市府內部網段 | + +## 兩個必須注意的雷 + +### 1. 不能用 `REMOTE_ADDR`,必須用 `X-Forwarded-For` + +站台前面是 Nginx Proxy Manager(openresty)反向代理,後端 Apache 看到的 `REMOTE_ADDR` 永遠是 NPM 容器的 docker 內網 IP(每個 client 都一樣)。直接用 `REMOTE_ADDR` 比對 → 不是把所有人都放行就是把所有人都擋掉。 + +NPM 預設行為:把真實 client IP **append 到 `X-Forwarded-For` 尾端**,所以末尾錨定 `$` 可以同時: +- 比對到真正的 client IP +- 防止 client 自己送假 header 偽造前綴繞過(任何前綴都會被 NPM 推到非末尾位置) + +替代方案:在 Apache 啟用 `mod_remoteip` 並信任 NPM 來源 → `REMOTE_ADDR` 會被替換為真實 client IP,這樣原本的 `REMOTE_ADDR` 規則才能用。目前**沒有**啟用,故採取 `X-Forwarded-For` 方案。 + +### 2. `RewriteCond` regex 不能放 literal space + +Apache 用空白切 directive 欄位,regex 裡寫 `(^|, ?)` 會被 parser 截斷 → vhost 整個壞掉,`/admin/` 會回 500。必須用 `\s?`、`[ ]?` 或 `\ `(escape)。本檔採 `\s?`。 + +## 更新 SOP + +新增 / 移除 IP,照下面流程在本機跑: + +```bash +# 1. 備份(檔名用當天日期) +ssh hospital_web 'sudo cp -p /opt/php/html/admin/.htaccess /opt/php/html/admin/.htaccess.bak.YYYYMMDD' + +# 2. 寫入新內容(編輯下面 here-doc 內的規則) +cat <<'HTACCESS' | ssh hospital_web 'sudo tee /opt/php/html/admin/.htaccess > /dev/null' +RewriteEngine On + +RewriteCond %{HTTP:X-Forwarded-For} !(^|,\s?)61\.220\.124\.10[56]$ +RewriteCond %{HTTP:X-Forwarded-For} !(^|,\s?)220\.135\.27\.129$ +# ... 其餘規則 ... +RewriteRule .* https://tpesupporter.tpech.gov.tw/404 [R,L] +HTACCESS + +# 3. 修正擁有者(檔案掛載自 host,需保持原 uid) +ssh hospital_web 'sudo chown tpech_1duan:tpech_1duan /opt/php/html/admin/.htaccess && sudo chmod 664 /opt/php/html/admin/.htaccess' +``` + +> Apache 每次請求都會重讀 `.htaccess`,**不需要 restart 容器**。 + +## 驗證 + +兩個方向都要測,缺一不可: + +```bash +# 負向:從非白名單 IP(例如本機)→ 預期 302 +curl -sI https://tpesupporter.tpech.gov.tw/admin/ | head -5 + +# 正向:從白名單 IP(例如 jason_hsieh_home_srv = 220.135.27.129)→ 預期 200 +ssh jason_hsieh_home_srv 'curl -sI https://tpesupporter.tpech.gov.tw/admin/ | head -5' +``` + +> 從外部用 `-H "X-Forwarded-For: <白名單IP>"` **無法**模擬白名單 client,因為 NPM 會把你的真實 IP append 在尾端覆蓋掉錨點 — 這是設計上正確的安全行為,不是 bug。 + +## 出錯時的回退 + +```bash +ssh hospital_web 'sudo cp /opt/php/html/admin/.htaccess.bak.YYYYMMDD /opt/php/html/admin/.htaccess' +``` + +如果 `/admin/` 出 500,幾乎可以斷定是 `.htaccess` 語法錯誤(最常見:regex 含 literal space)。直接回退舊版即可。