Môi trường — GIW POC
Trạng thái: CHỈ CHẠY LOCAL · Ngày: 21/09/2026 Quy định đang áp dụng: chạy local trên máy của Founder. Không cloud, không môi trường test, không server dùng chung, không production, không hosting bên ngoài.
Tài liệu này tách cái đang chạy hôm nay khỏi cái mà một môi trường thật sẽ cần, để hai bên không lẫn vào nhau.
1. LOCAL — môi trường duy nhất đang tồn tại
Docker Compose, localhost, service giả lập, dữ liệu tổng hợp.
Các endpoint
Mọi cổng đều bind vào 127.0.0.1. Không thứ gì với tới được từ LAN hay Internet.
| Service | URL | Xác thực | Ghi chú |
|---|---|---|---|
| Wi-Fi Portal | http://localhost:3000 | không | Host duy nhất mà trình duyệt liên hệ |
| › ứng dụng (mọi màn) | http://localhost:3000/ | không | Portal tự render. Không tồn tại trang Keycloak nào. |
| › login | POST http://localhost:3000/api/v1/auth/login |
không | Đặt cookie sid |
| › register | POST http://localhost:3000/api/v1/auth/register |
không | |
| › xác minh nhân viên | POST http://localhost:3000/api/v1/auth/employee |
không | |
| › trạng thái phiên | GET http://localhost:3000/api/v1/auth/session |
cookie sid |
Chỉ trả token gốc khi DEBUG_EXPOSE_TOKEN=1 |
| › refresh | POST http://localhost:3000/api/v1/auth/refresh |
cookie sid |
|
| › logout | POST http://localhost:3000/api/v1/auth/logout |
cookie sid |
Thu hồi cả phiên mạng |
| › meta | GET http://localhost:3000/api/v1/meta |
không | |
| › health | http://localhost:3000/health | không | |
| Keycloak (Galaxy ID) | http://localhost:8080 | admin / admin |
Admin console. Không nằm trong hành trình người dùng nào. |
| › realm discovery | http://localhost:8080/realms/galaxy-id-poc/.well-known/openid-configuration | không | |
| › JWKS | http://localhost:8080/realms/galaxy-id-poc/protocol/openid-connect/certs | không | |
| › health (readiness) | http://localhost:9000/health/ready | không | Cổng riêng — không phải 8080 |
| Mock HR API | http://localhost:3001 | X-API-Key |
|
| › verify | POST http://localhost:3001/api/v1/employees/verify |
X-API-Key |
|
| › tiêm lỗi | POST http://localhost:3001/admin/fault |
không | Công cụ test. Không được tồn tại trong môi trường thật. |
| › health | http://localhost:3001/health | không | |
| Entitlement Service | http://localhost:3002 | Bearer JWT | |
| › evaluate | POST http://localhost:3002/api/v1/entitlements/evaluate |
Bearer JWT | |
| › read | GET http://localhost:3002/api/v1/entitlements/{id} |
Bearer JWT | |
| › health | http://localhost:3002/health | không | |
| Session Service | http://localhost:3003 | X-API-Key |
|
| DMN Decision Service | http://localhost:3005 | không | Luật lấy từ file .dmn trên đĩa |
| › evaluate | POST http://localhost:3005/api/v1/decisions/{model}/evaluate |
không | |
| › liệt kê model | GET http://localhost:3005/api/v1/decisions |
không | Cho biết mỗi model đến từ file nguồn nào |
| › reload | POST http://localhost:3005/api/v1/decisions/reload |
không | ⚠️ không xác thực |
| › create / grant / revoke / read | /api/v1/sessions* |
X-API-Key |
|
| › health | http://localhost:3003/health | không | |
| Module giw-admin | http://localhost:8080/realms/galaxy-id-poc/giw-admin/ | Bearer (realm, manage-users) |
Vỏ GUI là công khai; mọi lệnh gọi /api đều xác thực |
| › webhook | POST .../giw-admin/webhook/{company} |
HMAC trên raw body | Không phải endpoint bearer — bên gọi là công ty thành viên |
| Mail sink | http://localhost:3008 | không | Hộp thư trình diễn. Service này không có socket đi ra |
| › SMTP | localhost:1025 | không | Từ chối thẳng AUTH và STARTTLS thay vì giả vờ |
| › messages | GET/DELETE http://localhost:3008/api/v1/messages |
không | Nằm trong bộ nhớ, có giới hạn, mất khi restart |
| Postgres | không publish | — | Chỉ trong mạng container |
Các cổng do biến môi trường quyết định (PORT_PORTAL, PORT_HR, PORT_ENTITLEMENT, PORT_SESSION, PORT_KEYCLOAK, PORT_KEYCLOAK_HEALTH, PORT_DMN, PORT_LOYALTY, PORT_CONSENT, PORT_MAIL_SINK, PORT_SMTP), và địa chỉ bind là BIND_ADDR, mặc định 127.0.0.1.
Theme của realm và đích SMTP do bootstrap áp từ GIW_THEME, SMTP_HOST, SMTP_PORT và SMTP_FROM — không ghi vào realm-export.json, nên cùng một file realm chạy được dù mail sink có bật hay không, và môi trường khác có thể trỏ đi nơi khác mà không phải sửa file đã commit.
Đừng đặt
BIND_ADDR=0.0.0.0. Trên mạng quán cà phê, khách sạn hay văn phòng, việc đó công khai Keycloak vớiadmin/admin, một endpoint tiêm lỗi không xác thực và một cổng SMTP mở cho mọi máy trong cùng subnet.
Chạy
cd poc/identity
cp .env.example .env
docker compose up -d --build
node tests/e2e.mjs
Local cố ý là như thế nào
| Khía cạnh | Local | Vì sao chấp nhận được ở đây |
|---|---|---|
| Đường truyền | HTTP thuần | chỉ loopback; không gì đi qua dây |
| Secret | giá trị mặc định yếu trong .env |
khởi động không cần can thiệp; .env đã git-ignore |
| Dữ liệu | tổng hợp | không người thật, không hồ sơ nhân viên, không số căn cước thật |
| Kho entitlement | trong bộ nhớ | mất khi restart, và với một bản demo thì không sao |
| Kho session | trong bộ nhớ | như trên |
| HR | service giả lập | hệ nhân sự gốc thật chưa được chốt (T-HR-SOR) |
| Xác thực giữa service | X-API-Key dùng chung (giữa các service) + client secret (Galaxy ID) |
không có bên nào không tin cậy trên mạng |
| Grant | Direct Grant — CHỈ CHO POC | xem docs/AUTH-STRATEGY.md. Đường production là browser OIDC / federation, TBC |
| Quan sát hệ thống | log JSON ra stdout | docker logs là toàn bộ công cụ |
2. Ranh giới cấu hình — vì sao sau này dịch chuyển được
Điểm của các quy tắc dưới đây là: một môi trường tương lai cần giá trị mới, không cần kiến trúc mới.
| Quy tắc | Đang được tôn trọng thế nào |
|---|---|
| Cấu hình qua biến môi trường | Mọi service đọc host, port, URL, timeout, TTL và credential từ env. Không file cấu hình nào được đọc lúc chạy, trừ realm-export.json vốn là dữ liệu mồi. |
Không hard-code localhost trong business logic |
localhost chỉ xuất hiện như giá trị mặc định của env. Kiểm chứng: grep -rn localhost */src trả về bốn dòng, tất cả đều là process.env.X || "http://localhost:…". |
| Mỗi URL một chỗ định nghĩa | PORTAL_BASE_URL là định nghĩa duy nhất của địa chỉ portal. bootstrap đối chiếu các client Keycloak theo nó ở mỗi lần khởi động, nên không bao giờ phải sửa realm-export.json. |
| Địa chỉ cho trình duyệt và địa chỉ trong container tách nhau | Portal gọi Galaxy ID qua OIDC_INTERNAL_BASE trên mạng container, còn token mang KC_HOSTNAME làm iss và Entitlement Service kiểm theo giá trị đó. Hai biến, có chủ đích. Gộp chúng lại là lỗi kinh điển của captive portal. |
| Không secret nào nằm trong repository | Client secret của API đến Keycloak qua OIDC_API_CLIENT_SECRET và do bootstrap áp; realm-export.json chỉ chứa một giá trị giữ chỗ. |
.env.example |
Có sẵn, có chú thích, mọi giá trị đều là giữ chỗ. .env đã git-ignore. |
| Docker Compose là hợp đồng vận hành | Một lệnh bật, một lệnh tắt. |
| OpenAPI không dính chặt vào localhost | servers dùng biến {scheme}://{host}:{port}; localhost là mặc định, không phải là đặc tả. |
| Giả định production nằm tách khỏi code | Tài liệu này, cộng api/API-OVERVIEW.md §7 (giả định POC), api/API-SECURITY.md §11 (khoảng trống), RUNBOOK.md §9 (checklist trước khi deploy). Không tuyên bố nào về production nằm bên trong một service. |
3. SAU NÀY — một môi trường thật cần thêm gì
Không thứ nào dưới đây đã được xây, và không thứ nào nên được xây trước khi một môi trường thật được phê duyệt.
| Hạng mục | Cần gì | Bị chặn bởi |
|---|---|---|
| TLS | sslRequired: external, KC_HOSTNAME dạng https, cookie sid có Secure, HSTS |
một tên miền |
| DNS / tên miền | hostname thật cho portal và Keycloak | một tên miền |
| Hồ sơ triển khai | overlay docker-compose.prod.yml hoặc Helm chart |
một môi trường |
| Cơ sở dữ liệu | Postgres có quản lý cho Keycloak; một kho thật cho entitlement và session | T-STORE |
| Quản lý secret | vault hoặc kho secret trên cloud; xoay vòng mọi giá trị trong .env |
một môi trường |
| Xác thực giữa service | thay cả hai chặng X-API-Key bằng mTLS hoặc service token ngắn hạn |
— |
| Audience | mỗi resource server một audience riêng; ngừng chấp nhận account |
T-AUD |
| Giới hạn tần suất | Entitlement và Session chưa có | — |
| Quan sát hệ thống | metric, tracing, cảnh báo, chuyển log về trung tâm | một môi trường |
| Sẵn sàng cao (HA) | nhiều hơn một replica; kho trong bộ nhớ khiến hôm nay không làm được | T-STORE |
| Tích hợp HR thật | hợp đồng, mô hình xác thực, SLA, phiên bản hoá, contract test | T-HR-SOR, D7 |
| Thu thập sự đồng ý | bắt buộc cho đường CCCD theo Luật 91/2025 | T-CCCD |
| Quyền truy cập / xoá dữ liệu của chủ thể | production bắt buộc | D7 |
| Gỡ bộ công cụ test | xoá POST /admin/fault; đặt DEBUG_EXPOSE_TOKEN=0 |
— |
| Xác thực | Chuyển từ Direct Grant (Mode A) sang browser OIDC / federation (Mode B) | T-GRANT — Direct Grant chỉ dành cho POC |
Checklist đầy đủ trước khi triển khai ở RUNBOOK.md §9.
Các chế độ xác thực và lộ trình chuyển đổi: AUTH-STRATEGY.md.
4. Những lằn ranh không được vượt
Đây không phải mục "để sau". Đây là điều kiện phải đạt trước khi bước tương ứng được thực hiện.
| Lằn ranh | Điều kiện |
|---|---|
| Không trỏ stack này vào hệ thống HR thật | D7 (Bên kiểm soát / Bên xử lý dữ liệu) còn mở |
| Không nạp dữ liệu cá nhân thật | chưa có bằng chứng đồng ý, chưa có chính sách lưu trữ, chưa có đường xoá |
Không mở cổng nào ra ngoài 127.0.0.1 |
Keycloak admin đang là admin/admin; /admin/fault không xác thực |
| Không coi đây là kiến trúc đã duyệt | cả 11 ADR đều đang ở trạng thái PROPOSED |
| Không đưa luồng CCCD lên production | ADR-003 và D3 khuyến nghị bỏ nó; xem AI_CONTEXT.md §11b |
| Không đưa Direct Grant lên production | RFC 9700 nói password grant MUST NOT be used; CHỈ CHO POC, xem AUTH-STRATEGY.md |
| Không xoá đường browser / PKCE | Đó là PRODUCTION CANDIDATE và là lối duy nhất dẫn tới liên kết IdP |