Tích hợp với Access Hub
Đây là đặc tả phần việc trong Access Hub (Laravel) cho trưởng nhóm Access Hub, theo cùng cách repo Access Hub Collector đặc tả HUB-1 đến HUB-18 (access-hub-collector/docs/06-access-hub-integration.md). Repo này không sửa Access Hub. Tên bảng, quyền, đường dẫn là đề xuất, chốt khi làm HUB-SEC-1.
1. Nguyên tắc
- Access Hub là nguồn sự thật cho vòng đời finding (trạng thái, ngoại lệ, SLA, người phụ trách), cài đặt, quyền, thông báo. Secmatch giữ inventory và kết quả so khớp thô (ADR 0008).
- Mọi model theo tenant dùng
CompanyScope, uuid làm khóa công khai,Auditablekhi có thao tác người dùng, theo quy ướcServer,MonitoringAgent. - Không mã hóa finding, gói, kết quả kiểm tra: chúng phải tìm kiếm, lọc được. Chỉ bí mật (token dịch vụ của secmatch nếu lưu trong DB, bí mật webhook đã có) đi qua
CompanyCrypto(ENC-2, nhánhfeat/biz-0). - Một công ty đang hoạt động tại một thời điểm, không có danh sách chéo công ty. Công ty mẹ chỉ xem công ty con khi được cấp quyền theo cơ chế hiện có.
- Nhân viên nền tảng không có đường đọc dữ liệu quét của khách hàng (không màn hình, không phiên truy cập); chỉ có thống kê ẩn danh (mục 8).
- Gọi secmatch qua một client có timeout ngắn, thử lại, ngắt mạch, giống
MonitoringCollectorClient(app/Services/Monitoring/Collector/MonitoringCollectorClient.php). Secmatch không sẵn sàng thì giao diện hiện "Không có dữ liệu", không lỗi 500.
2. Bảng dữ liệu (MySQL của Access Hub)
| Bảng | Cột chính | Ghi chú |
|---|---|---|
security_settings | company_id unique, enabled, inventory_interval_hours, checks_interval_hours, checks_enabled, disabled_checks json, walk_exclude json, thresholds json, sla_days json, severity_overrides json, internet_facing_tag, db_stale_days, content json, fp_company_key_encrypted, fp_company_key_version | Theo công ty. Giá trị bị kẹp theo gói (mục 6). content là cấu hình quét nội dung (07 mục 8); fp_company_key_encrypted là K_company mã hóa bằng CompanyCrypto (ENC-2), không bao giờ trả ra API hay giao diện |
security_findings | id uuid, company_id, server_id, finding_key, kind, vuln_id, aliases json, advisory_ids json, ecosystem, package_key, packages json, installed_version, fixed_version, fix_status, fix_channel, minor, kernel, check_id, check_subject, detector_id, detector_version, file_path, file_line, file_column, match_count, preview, fingerprint, confidence, exposure, file_modified_at, severity_label, cvss_score, cvss_vector, score_source, epss, epss_percentile, kev, kev_due_date, f_exposure, f_env, risk, priority, status, exception_id, first_seen_at, last_seen_at, fix_available_at, sla_due_at, sla_paused_seconds, resolved_at, resolved_reason, reboot_required, last_event_seq, db_version | CompanyScope. kind gồm vulnerability, check, secret, sensitive_data. Unique (company_id, finding_key). Chỉ mục (company_id, status, priority), (company_id, server_id, status), (company_id, vuln_id), (company_id, package_key), (company_id, sla_due_at), (company_id, kind, status), (company_id, fingerprint). Cột nội dung chỉ chứa các trường ADR 0011, không có cột giá trị |
security_finding_events | id, company_id, finding_id, event_id unique, seq, type, occurred_at, payload json | Nhật ký, khử trùng theo event_id, dọn theo security_history_days |
security_exceptions | id uuid, company_id, type, scope_kind, server_id, tag, vuln_id, package_key, check_id, reason, ticket_ref, requested_by, approved_by, self_approved, status, decided_at, expires_at, revoked_at, revoked_by | CompanyScope, Auditable |
security_server_scans | company_id, server_id unique, last_event_seq, last_scanned_at, scanner_version, db_version, os_id, os_version, os_codename, match_status, release_support, kernel_running, livepatch_active, open_findings, package_count, walk_truncated, error_codes json, listening json, reboot_required, server_risk, open_p1, open_p2, open_p3, open_p4, content_stats json, content_baseline_complete, open_secrets_high, pii_files | Một dòng mỗi máy, phục vụ dashboard và tab máy chủ |
scan_hosts | id, uuid, company_id, server_id nullable, host_id unique, public_key, prev_public_key nullable, prev_key_expires_at nullable, machine_id_hash, hostname, os_id, os_version, uploader_version, state, version, scan_requested_at nullable, enrolled_at, last_seen_at, revoked_at, revoked_by, revoked_reason | Máy Scanner (ADR 0001): danh tính của gói Scanner trên một máy, độc lập với monitoring_agents. CompanyScope, Auditable. state: active, revoked. Đếm vào hạn mức scan_hosts. Unique (company_id, machine_id_hash) cho máy chưa thu hồi. server_id gắn với Server chung, nên máy cài cả giám sát và Scanner có cả MonitoringAgent và scan_hosts trỏ cùng một Server |
scan_enrolment_codes | id uuid, company_id, server_id nullable, token_hash, expires_at, used_at, used_by_host_id, created_by, revoked_at | Mã enroll một lần dùng, băm SHA-256, chỉ hiện bản rõ một lần lúc tạo, hết hạn 24 giờ. CompanyScope, Auditable |
security_check_states | company_id, server_id, check_id, subject, check_version, status, evidence json, observed_at | Unique (server_id, check_id, subject) |
security_expected_ports | id uuid, company_id, scope json, proto, port_from, port_to, bind_scope, note, created_by | CompanyScope, Auditable. scope giải theo máy, tag, vai trò, môi trường như MonitoringRuleScopeResolver |
security_stats_daily | company_id, date, open_by_priority json, opened, resolved, overdue, accepted, false_positive, kev_open, servers_scanned, servers_stale, mttr_seconds_by_priority json, sla_compliance, checks_pass_rate json | Unique (company_id, date) |
security_vulnerabilities | vuln_id unique, summary, published_at, modified_at, cvss json, cwe json, references json, kev, epss, epss_percentile, updated_at | Toàn cục (dữ liệu tham chiếu công khai, không thuộc công ty). Không có endpoint liệt kê cho tenant: tenant chỉ đọc qua join với finding của chính mình, để không lộ "có công ty nào đó dính CVE X" |
platform_security_stats_daily | date unique, metrics json | Không có company_id. Chỉ số tổng hợp ẩn danh (mục 8) |
Gói, dịch vụ, phần mềm ngoài gói, thống kê tệp và tệp đáng chú ý theo máy không lưu trong MySQL của Access Hub (Q4): tra qua secmatch (07 mục 7). Finding nội dung lưu trong security_findings vì có vòng đời.
Ghi theo lô: security_findings nhận lô sự kiện đến 200 mục, cập nhật bằng upsert theo (company_id, finding_key) trong một giao dịch mỗi lô. last_event_seq chặn áp sự kiện cũ.
3. Quyền (PermissionCatalog)
Nhóm mới security trong app/Support/Authorization/PermissionCatalog.php:
| Quyền | Phạm vi | Dùng cho |
|---|---|---|
security.view | tenant | Xem tổng quan, finding, kết quả kiểm tra, gói, ngoại lệ |
security.manage | tenant | Cài đặt quét, cổng dự kiến, ngưỡng, ghi đè mức độ, yêu cầu quét sớm |
security.hosts.manage | tenant | Tạo, thu hồi mã enroll Scanner; xem, gắn Server, thu hồi máy Scanner |
security.exceptions.request | tenant | Tạo yêu cầu ngoại lệ |
security.exceptions.approve | tenant | Duyệt, từ chối, thu hồi ngoại lệ (không duyệt yêu cầu của chính mình, trừ gói cá nhân) |
security.export | tenant | Xuất CSV, XLSX, PDF |
security.secrets.view | tenant | Xem đường dẫn, preview, chi tiết finding bí mật; xuất có cột bí mật. Không có quyền này chỉ thấy số đếm |
security.pii.view | tenant | Xem bản đồ dữ liệu cá nhân, đường dẫn và số đếm theo tệp. Không có quyền này chỉ thấy tổng số |
security.content.manage | tenant | Bật quét nội dung, dữ liệu cá nhân, chọn hồ sơ, gốc tùy chọn, khung giờ, detector tắt; mọi thay đổi có audit |
security.matcher | service | Chỉ tài khoản dịch vụ của secmatch. Phải được xử lý như COLLECTOR_PERMISSION hiện có: vai trò tùy chỉnh không chứa được, wildcard administrator bỏ qua, chỉ super admin cấp |
platform.security.stats.view | platform | Xem thống kê ẩn danh ở bảng điều khiển nền tảng |
Vai trò có sẵn (đề xuất): administrator nhận mọi quyền tenant ở trên; infrastructure-engineer nhận thêm security.hosts.manage; auditor nhận security.view, security.pii.view; infrastructure-engineer nhận security.view, security.secrets.view, security.exceptions.request; vai trò dịch vụ mới security-matcher (SCOPE_SERVICE) chỉ có security.matcher; platform-ops, platform-auditor nhận platform.security.stats.view.
4. API
4.1 API cho secmatch (routes/security-matcher.php)
Tiền tố /api/v1/security/matcher, require từ routes/api.php thành nhóm riêng ngoài nhóm throttle:iac-api (như routes/monitoring-collector.php đã làm), Sanctum + allowlist IP của token, rate limiter riêng security-matcher.
| Method, đường dẫn | Mục đích |
|---|---|
POST /events | Nhận lô sự kiện (07 mục 6), tối đa 200, idempotent |
PUT /vulnerabilities | Upsert lô dữ liệu tham chiếu CVE (tóm tắt, CVSS, KEV, EPSS) cho các CVE có trong finding |
GET /settings/{company_id} | Cài đặt quét đã kẹp theo gói, có version; vai trò ingest dựng SecurityConfig từ đây (07 mục 8) |
POST /scan-hosts/enroll | Ingest đổi mã enroll lấy máy Scanner: kiểm mã, tính năng security_scanning, hạn mức scan_hosts, gắn hoặc tạo Server; trả host_id, company_id, server_id (07 mục 3.0) |
GET /scan-hosts?since= | Bản sao registry cho ingest: khóa công khai, trạng thái, company_id, server_id, trạng thái giấy phép, version, scan_requested_at |
POST /scan-hosts/{host_id}/rotate, POST /scan-hosts/{host_id}/seen | Xoay khóa công khai; cập nhật last_seen_at theo lô |
POST /heartbeat | Phiên bản, db_version, db_stale, độ sâu outbox, hiển thị cho nền tảng (không dữ liệu khách hàng) |
Cấu hình quét đến máy qua vai trò ingest của secmatch (GET /scan/v1/config), không qua Collector (Q25).
4.2 API v1 cho người dùng (routes/api/security.php)
| Chức năng | Endpoint (tóm tắt) | Quyền |
|---|---|---|
| Tổng quan, xu hướng | GET /api/v1/security/overview, /trends | security.view |
| Finding: danh sách có lọc, chi tiết, lịch sử | /api/v1/security/findings | security.view |
| Bảo mật của máy, gói của máy | /api/v1/servers/{uuid}/security, /api/v1/servers/{uuid}/packages | security.view + servers.view |
| Tìm máy theo gói | GET /api/v1/security/packages/search | security.view |
| Ngoại lệ: tạo, duyệt, từ chối, thu hồi | /api/v1/security/exceptions | .request, .approve |
| Cài đặt, cổng dự kiến | /api/v1/security/settings, /api/v1/security/expected-ports | security.manage |
| Xuất | POST /api/v1/security/exports | security.export (cột nội dung cần thêm quyền xem tương ứng) |
| Finding bí mật, "cùng bí mật trên N máy" | /api/v1/security/secrets, /api/v1/security/secrets/{uuid}/servers | security.secrets.view |
| Bản đồ dữ liệu cá nhân | /api/v1/security/personal-data (theo máy, tệp, loại) | security.pii.view |
| Cài đặt quét nội dung | /api/v1/security/settings/content | security.content.manage |
| Dịch vụ, phần mềm của máy; tìm máy theo dịch vụ, phần mềm | /api/v1/servers/{uuid}/services, /api/v1/servers/{uuid}/software, /api/v1/security/software/search | security.view + servers.view |
Theo quy ước dự án: kiểm thử Pest cho mỗi endpoint, mẫu chạy thật tests/ApiSamples/NN_security.php, authorizeAbility(), QueuesApiExport cho xuất, HandlesApiTrash nếu cổng dự kiến dùng thùng rác.
5. Trang giao diện (Inertia, Vue)
| Trang | Đường dẫn đề xuất | Ghi chú |
|---|---|---|
| Tổng quan bảo mật | resources/js/pages/security/overview/Index.vue | 06 mục 5.1 |
| Danh sách finding, chi tiết | security/findings/Index.vue, Show.vue | Bộ lọc máy dùng ô chọn tìm kiếm bất đồng bộ có phân trang |
| Ngoại lệ | security/exceptions/Index.vue, Create.vue | Biểu mẫu theo phong cách tạo mới hiện hành của dự án (nhiều biểu tượng, thẻ radio, chỉ báo bước như iac/Request.vue) |
| Cài đặt, cổng dự kiến | security/Settings.vue, security/expected-ports/* | Như trên |
| Tab Bảo mật của máy chủ | thêm vào servers/Show.vue | 06 mục 5.3 |
| Finding bí mật | security/secrets/Index.vue, Show.vue | Mức độ theo phơi nhiễm, "cùng bí mật trên N máy", hướng dẫn xoay vòng; mở chi tiết ghi audit |
| Bản đồ dữ liệu cá nhân | security/personal-data/Index.vue | Theo máy, tệp, loại, chỉ số đếm; độ phủ quét |
| Cài đặt quét nội dung | security/settings/Content.vue | Biểu mẫu theo phong cách tạo mới hiện hành (thẻ radio cho hồ sơ, chỉ báo bước); cảnh báo rõ khi thêm gốc tùy chọn |
| Tab Phần mềm và Dịch vụ của máy | thêm vào servers/Show.vue | P6 |
| Thống kê nền tảng | platform/security/Index.vue | Chỉ số ẩn danh |
| Trang tài liệu trong ứng dụng | trang docs công khai (kỹ thuật và hướng dẫn, vi và en) | Gồm trang "Nguồn dữ liệu" với ghi công, thông báo NVD (03 mục 4) |
6. Gói và hạn mức
Hạn mức đọc qua QuotaService::limit() và tính năng qua cơ chế tính năng của gói (PlanCatalog::FEATURE_KEYS, nhánh feat/biz-0). Scanner là gói cước riêng (Q25): số máy quét được đếm bằng hạn mức mới scan_hosts (máy Scanner chưa thu hồi), không dùng monitoring_agents. Enroll vượt hạn mức bị từ chối; hạ gói làm vượt hạn mức thì máy enroll sau cùng chuyển licence_inactive cho tới khi có chỗ.
Đề xuất khóa mới (chủ dự án chốt, Q10):
| Khóa | Loại | Free | Pro | Expert | Team | Business | Dedicated |
|---|---|---|---|---|---|---|---|
security_scanning | tính năng | có | có | có | có | có | có |
scan_hosts | LIMIT_KEYS (đếm) | 1 | 5 | 20 | 50 | 200 | theo DeploymentLicense |
security_hardening (kiểm tra AHS-*) | tính năng | không | có | có | có | có | có |
security_exceptions_approval (duyệt hai người) | tính năng | không áp dụng (tự duyệt) | không áp dụng | không áp dụng | có | có | có |
security_reports_pdf | tính năng | không | không | có | có | có | có |
security_custom_sla | tính năng | không | có | có | có | có | có |
security_history_days | SETTING_LIMIT_KEYS | 30 | 90 | 180 | 180 | 365 | không giới hạn |
security_scan_min_interval_hours | SETTING_LIMIT_KEYS | 24 | 24 | 6 | 6 | 1 | 1 |
security_secret_scanning | tính năng | không (chỉ số đếm) | có | có | có | có | có |
security_pii_scanning | tính năng | không | không | có | có | có | có |
security_software_inventory (dịch vụ, phần mềm, tệp đáng chú ý) | tính năng | có | có | có | có | có | có |
security_content_custom_roots | SETTING_LIMIT_KEYS | 0 | 4 | 8 | 8 | 16 | 16 |
- Free, Pro, Expert có
users = 1trongPlanCatalog::defaults(), nên duyệt hai người không thể có: tự duyệt có lý do và hạn (06mục 6). - Dedicated lấy hạn mức và tính năng từ
DeploymentLicense(payload ký, kiểm tra offline): khóa mới phải có trong payload giấy phép triển khai. - Hạ gói: tính năng tắt thì ngừng quét mới, dữ liệu cũ chỉ đọc; xóa theo
security_history_daysmới sau thời gian ân hạn 30 ngày, cùng chính sách đã chốt cho retention số liệu giám sát (Q17 của Collector). - Đổi gói nâng
versioncủa cài đặt và registry máy Scanner; ingest thấy ở lần đồng bộ kế tiếp (60 giây) và đổi ETagGET /scan/v1/config.
7. Thông báo, webhook, audit
Mẫu thông báo mặc định (vi, en) qua NotificationTemplate, NotificationPreference, WebhookChannel hiện có:
| Sự kiện | Mặc định gửi tới |
|---|---|
security.finding.p1_opened (gồm CVE vào KEV làm finding thành P1) | Người có security.manage |
security.digest.daily (finding ưu tiên P2 mới, đã đóng, sắp quá hạn) | Người có security.view đã đăng ký |
security.sla.due_soon, security.sla.overdue | Người có security.manage |
security.exception.requested, .decided, .expiring, .expired | Người duyệt, người yêu cầu |
security.server.scan_stale (máy Scanner đang hoạt động mà quá 48 giờ không có báo cáo) | Người có security.manage |
security.db.stale (Dedicated, DB quá ngưỡng) | Quản trị viên công ty on-prem |
security.secret.high_opened (bí mật mức critical hoặc high mới) | Người có security.secrets.view. Nội dung: mã detector, mức độ, tên máy, liên kết; không đường dẫn, preview, fingerprint |
security.pii.bulk_found (tệp mới có dữ liệu cá nhân từ ngưỡng thứ hai) | Người có security.pii.view. Chỉ số đếm và tên máy |
security.content.scope_changed (gốc tùy chọn, bật dữ liệu cá nhân) | Mọi người có security.content.manage |
Payload webhook: uuid finding, uuid và tên máy, vuln_id hoặc detector_id, ưu tiên, risk, fixed_version, liên kết. Không bí mật, không bằng chứng thô, không đường dẫn, preview, fingerprint của finding nội dung. Chuông thời gian thực (Reverb) cho P1. Audit qua AuditRecorder: cài đặt (gồm phạm vi quét nội dung, xoay K_company), cổng dự kiến, ngoại lệ (tạo, duyệt, từ chối, thu hồi), xuất, mở chi tiết finding bí mật (Q41). Sự kiện finding do hệ thống sinh ghi vào security_finding_events, không ghi audit từng sự kiện.
8. Thống kê nền tảng ẩn danh
Job ban đêm chạy trong ngữ cảnh hệ thống, đọc security_findings, security_server_scans (đếm theo company_id chỉ trong bộ nhớ để áp ngưỡng k), ghi platform_security_stats_daily không có định danh công ty. Chỉ số nội dung chỉ là số đếm theo họ detector, mức độ, mức phơi nhiễm; không đường dẫn, fingerprint, preview. Ngưỡng k mặc định 5 (Q13). Trang nền tảng chỉ đọc bảng này và GET /internal/v1/stats/global của secmatch. Kiểm thử: bảng không có cột định danh công ty, mục dưới ngưỡng k không xuất hiện, người có vai trò nền tảng không gọi được endpoint tenant.
9. Vòng đời và dọn dẹp
- Xóa vĩnh viễn
Server: trong cùng giao dịch xóasecurity_*của máy; sau commit dispatch job gọiDELETE /internal/v1/servers/{id}/data?company_id=của secmatch (tries10, backoff như HUB-14). - Purge
Company(gồm từng công ty con): như trên vớiDELETE /internal/v1/companies/{id}/data, gọi một lần cho mỗicompany_id. - Sự kiện đến muộn cho máy đã xóa: trả
rejectedvớiunknown_server, không 5xx. - Lệnh dọn định kỳ theo mẫu lệnh dọn hiện có:
security_finding_eventsvà finding đã đóng quásecurity_history_days.
10. Danh sách việc HUB-SEC
| Mã | Việc | P |
|---|---|---|
| HUB-SEC-1 | Quyền nhóm security, vai trò dịch vụ security-matcher xử lý như monitoring.collector (vai trò tùy chỉnh không chứa được), quyền nền tảng | 1 |
| HUB-SEC-2 | Migration, model, factory cho bảng mục 2 (theo giai đoạn) | 1 đến 7 |
| HUB-SEC-3 | routes/security-matcher.php, rate limiter, POST /events, PUT /vulnerabilities, GET /settings, POST /heartbeat | 1 |
| HUB-SEC-4 | Tab Bảo mật trên trang máy chủ, SecmatchClient (timeout, thử lại, ngắt mạch) | 1 |
| HUB-SEC-5 | Tổng quan bảo mật, xu hướng, security_stats_daily | 3, 7 |
| HUB-SEC-6 | Danh sách finding, chi tiết, tìm máy theo gói | 1 (cơ bản), 3 (đầy đủ) |
| HUB-SEC-7 | Ngoại lệ: model, luồng duyệt, hết hạn tự động, giao diện; phạm vi detector cộng mẫu đường dẫn, fingerprint | 7 |
| HUB-SEC-8 | Mẫu thông báo vi, en, digest, webhook | 3 (ưu tiên P1, SLA), 7 (còn lại) |
| HUB-SEC-9 | Xuất CSV, XLSX (giai đoạn P3), PDF (giai đoạn P7, cần duyệt phụ thuộc mới) | 3, 7 |
| HUB-SEC-10 | API v1 routes/api/security.php, mẫu tests/ApiSamples | 3, 7 |
| HUB-SEC-11 | Gói và hạn mức: khóa mới trong PlanCatalog, kẹp cài đặt, payload DeploymentLicense, reload cấu hình khi đổi gói | 3 |
| HUB-SEC-12 | Thống kê nền tảng ẩn danh (gồm số đếm nội dung) | 7 |
| HUB-SEC-13 | Purge máy và công ty gọi secmatch | 1 |
| HUB-SEC-14 | Cài đặt quét, cổng dự kiến, ghi đè mức độ, GET /settings/{company_id} cho ingest | 5 |
| HUB-SEC-15 | Trang tài liệu trong ứng dụng (kỹ thuật và hướng dẫn, vi, en), trang Nguồn dữ liệu | 1 đến 7 |
| HUB-SEC-16 | Tính risk, priority, SLA phía Access Hub, tính lại khi thuộc tính máy đổi; phần 16b cho finding nội dung | 3 |
| HUB-SEC-17 | Quét bí mật: quyền security.secrets.view, security.content.manage, cột nội dung, K_company (sinh, ENC-2, phát xuống, xoay), cài đặt quét nội dung, trang finding bí mật, "cùng bí mật trên N máy", audit khi xem, thông báo không giá trị, tài liệu | 2 |
| HUB-SEC-18 | Dữ liệu cá nhân: quyền security.pii.view, bật theo công ty, bản đồ dữ liệu cá nhân, thông báo, tài liệu gồm phần nghĩa vụ của khách hàng | 4 |
| HUB-SEC-19 | Kiểm kê mở rộng: tab Phần mềm và Dịch vụ, tìm máy theo dịch vụ, phần mềm, thống kê tệp, tệp đáng chú ý | 6 |
| HUB-SEC-20 | Container, image | 9 |
| HUB-SEC-21 | Máy Scanner: bảng scan_hosts, scan_enrolment_codes; quyền security.hosts.manage; trang tạo mã enroll (hiện lệnh cài, mã một lần), danh sách máy Scanner, thu hồi; POST /scan-hosts/enroll, GET /scan-hosts?since=, xoay khóa; hạn mức scan_hosts; trang máy chủ hiển thị cả Access Hub Agent và máy Scanner (nếu có) với trạng thái riêng; kiểm thử hai công ty, mã hết hạn, dùng lại, vượt hạn mức | 1 |
Định nghĩa hoàn thành cho mỗi HUB-SEC: như danh sách hoàn thành của HUB-1 đến HUB-18 (access-hub-collector/docs/06 mục 9, chỉ mượn tiêu chí) (Pest, mẫu API, trang tài liệu vi và en, khóa dịch đủ hai locale, Pint, PHPStan, ESLint, vue-tsc, php artisan queue:restart khi đụng job hoặc thông báo), cộng kiểm thử hai công ty cho mọi endpoint và job, rà soát cô lập tenant trước khi hợp nhất, và với finding nội dung: kiểm thử rằng không API, trang, thông báo, xuất nào trả giá trị (chỉ các trường ADR 0011) và người thiếu quyền xem chỉ thấy số đếm.
11. Đặc tả chi tiết HUB-SEC giai đoạn P1
Mục này là bản đặc tả để nhóm Access Hub làm ngay phần P1, khớp với secmatch đã cài (internal/secmatch). Phần nào secmatch chưa gửi được ghi rõ là để sau; Access Hub không cần làm trước.
11.1 Phạm vi P1
| Mã | Phần P1 | Để sau |
|---|---|---|
| HUB-SEC-1 | Quyền security.view, security.matcher (dịch vụ), vai trò security-matcher | Quyền ngoại lệ, xuất, nội dung |
| HUB-SEC-2 | Bảng security_findings, security_finding_events, security_server_scans | Các bảng còn lại |
| HUB-SEC-3 | POST /api/v1/security/matcher/events, GET /settings/{company_id} (ingest cần) | PUT /vulnerabilities, POST /heartbeat: secmatch chưa gọi |
| HUB-SEC-4 | Tab Bảo mật của máy chủ, SecmatchClient | |
| HUB-SEC-6 | Danh sách finding cơ bản có lọc, tìm máy theo gói | Chi tiết đầy đủ, risk, SLA (P3) |
| HUB-SEC-13 | Xóa máy, purge công ty gọi secmatch | |
| HUB-SEC-21 | Mã enroll, registry máy Scanner, hạn mức scan_hosts, liên kết Server, API cho ingest | Xoay khóa tự động |
| HUB-SEC-15 | Trang tài liệu vi, en (gồm trang Nguồn dữ liệu, ghi công CC BY-SA 4.0 của Ubuntu) |
Không có risk, priority, SLA, ngoại lệ, thông báo trong P1. Giao diện hiển thị severity_label của distro và fix_status.
11.2 Phong bì và lô
POST /api/v1/security/matcher/events, Content-Type: application/json, Authorization: Bearer <Sanctum token của tài khoản security-matcher>.
{"events": [
{"event_id": "9c1e...64 hex", "type": "finding.opened", "company_id": "c1...", "server_id": "s1...",
"seq": 42, "occurred_at": "2026-10-02T13:00:00Z", "payload": {"...": "..."}}
]}- Tối đa 200 sự kiện mỗi lô. Secmatch chỉ đặt sự kiện của một công ty trong một lô, nhưng Access Hub vẫn kiểm từng sự kiện.
event_id: 64 ký tự hex, duy nhất toàn cục. Gửi lặp (secmatch thử lại sau 5xx, timeout) phải trả kết quả như lần đầu và không áp lại.seq: tăng chặt theo máy (chung cho mọi loại sự kiện của máy đó). Dùng để bỏ sự kiện cũ đến muộn.host_idluôn có;server_idlànullkhi máy Scanner chưa gắnServer(07mục 3.0), Access Hub gắn finding vào máy Scanner quahost_idcho tới khi gắn.company_id,server_id: lấy từ registry máy Scanner (scan_hosts), do Access Hub cấp lúc enroll;server_idlà định danh củaServerchung, không phảiagent_idgiám sát.
11.3 Payload theo loại
finding.opened và finding.updated (cùng hình dạng; updated gửi khi một trường trong danh sách "đổi" bên dưới thay đổi):
| Trường | Kiểu | Ghi chú |
|---|---|---|
finding_key | hex 64 | Khóa ổn định của finding, đã gồm company_id, server_id |
kind | "vulnerability", "release_eol" | Xem bảng bản phát hành hết hỗ trợ ở cuối mục |
vuln_id | chuỗi | CVE-...; Debian có thể có TEMP-... |
aliases, advisory_ids | mảng chuỗi | advisory_ids gồm USN-..., DSA-..., DLA-...; luôn là mảng |
ecosystem | chuỗi | ubuntu:24.04, debian:12 |
package_key | chuỗi | Gói nguồn (kernel: linux hoặc biến thể) |
packages | mảng {name, version, arch} | Gói nhị phân đã cài thuộc gói nguồn đó |
installed_version | chuỗi | Phiên bản thấp nhất đang cài của gói nguồn |
fixed_version | chuỗi, có thể rỗng | Rỗng khi chưa có bản sửa |
fix_status | fixed, no_fix, wont_fix, undetermined, upgrade_release | fixed nghĩa là có bản sửa mà máy chưa cài; upgrade_release chỉ cho kind = release_eol |
fix_channel | standard, esm, elts | esm: bản sửa chỉ có qua Ubuntu Pro; elts: chỉ qua Debian ELTS trả phí |
severity_label | chuỗi, có thể rỗng | Mức của distro, chữ thường: Ubuntu negligible, low, medium, high, critical; Debian là urgency của tracker viết thường, thường gặp unimportant, low, medium, high, not yet assigned, end-of-life |
minor | bool | Debian no-dsa, unimportant: ưu tiên thấp |
kernel | bool | Finding của kernel đang chạy |
reboot_required | bool | Bản sửa đã cài nhưng kernel cũ còn chạy |
no_distro_fix | bool | Bản phát hành hết hỗ trợ, distro sẽ không sửa |
eol_date, upgrade_to | chuỗi, mảng chuỗi | Chỉ có giá trị với kind = release_eol; rỗng ở finding khác |
cvss_score, cvss_vector, score_source | số, chuỗi, chuỗi | Chỉ có khi DB có điểm (NVD, chưa bật ở P1): trường có thể vắng |
kev, epss | false, null | Giữ chỗ, P3 |
first_seen_at | RFC3339 | Lần đầu secmatch thấy finding |
db_version | số | Phiên bản DB lỗ hổng đã dùng |
Trường "đổi" sinh finding.updated: installed_version, fixed_version, fix_status, fix_channel, severity_label, reboot_required, minor, packages (tên và phiên bản), advisory_ids. Điểm CVSS và bí danh đổi không sinh updated ở P1.
finding.resolved: finding_key, kind, resolved_reason (package_updated, package_removed, advisory_changed, out_of_scope, release_upgraded), resolved_at, db_version. out_of_scope nghĩa là máy không còn được so khớp (đổi sang hệ không hỗ trợ, bản phát hành hết hỗ trợ).
server.scanned: scanned_at, scanner_version, db_version, os {id, version_id, codename}, kernel_running, reboot_required, livepatch_active, package_count, error_codes (mảng, xem contract/security-report-v1.md mục 6), match_status (ok, unsupported_os, unsupported_release, release_eol, no_db), release_support (standard, esm_only, eol hoặc rỗng), open_findings. Chỉ gửi khi có báo cáo mới của máy (không gửi khi so khớp lại vì DB đổi).
Hiển thị match_status: "Hệ điều hành chưa được hỗ trợ", "Chưa có dữ liệu lỗ hổng", và với release_eol: "Bản phát hành đã hết hỗ trợ bảo mật". Chính sách của chủ dự án (2026-10-02): lỗ hổng vẫn là lỗ hổng. Máy chạy bản phát hành hết hỗ trợ vẫn nhận mọi finding (finding không bị đóng với out_of_scope), cộng một finding release_eol.
Finding của bản phát hành hết hỗ trợ:
| Trường | Ý nghĩa |
|---|---|
no_distro_fix (mọi finding, bool) | Distro sẽ không phát hành bản sửa: finding chưa sửa trên bản hết hỗ trợ, hoặc bản sửa chỉ có ở ELTS trả phí. Giao diện ghi "Distro sẽ không sửa" |
fix_channel = elts | Bản sửa chỉ có qua Debian ELTS trả phí (Freexian), như esm của Ubuntu Pro |
kind = release_eol | Một finding cho mỗi máy: vuln_id dạng AHS-EOL-DEBIAN-11, package_key rỗng, fix_status = upgrade_release, severity_label = high, eol_date (YYYY-MM-DD), upgrade_to (các bản phát hành đang hỗ trợ của distro, ví dụ ["debian:12", "debian:13"]). Hiển thị nổi bật trên tab Bảo mật và tổng quan: "Nâng cấp lên bản phát hành được hỗ trợ", kèm ngày hết hỗ trợ |
Finding release_eol đóng với resolved_reason = release_upgraded khi máy báo cáo bản phát hành khác (nâng cấp xong).
11.4 Xử lý mỗi lô
Trong một giao dịch mỗi lô:
- Kiểm phong bì: JSON hợp lệ,
eventslà mảng 1 đến 200, mỗi sự kiện có đủ trường với kiểu đúng. Sai phong bì thì 422 cả lô. - Với từng sự kiện:
event_idđã có trongsecurity_finding_events: đưa vàoaccepted, không làm gì.server_idkhông thuộccompany_id, hoặc máy đã xóa, hoặc máy Scanner của nó ở trạng tháirevoked:rejectedvớiunknown_server.typelạ:rejectedvớiunsupported_type. Payload thiếu trường bắt buộc:invalid_payload.- Sự kiện finding: tìm
security_findingstheo(company_id, finding_key). Nếu có vàseq <= last_event_seq: ghi nhật ký,accepted, không đổi finding (sự kiện cũ).opened,updated: upsert mọi cột của payload,status = open,resolved_at,resolved_reasonvề null,last_seen_at = occurred_at,last_event_seq = seq.openedcho finding đangresolvedlà mở lại.updatedcho finding chưa có thì tạo nhưopened(secmatch có thể phát lại sau sự cố).resolved: nếu có thìstatus = resolved,resolved_at,resolved_reason,last_event_seq = seq; nếu chưa có thì chỉ ghi nhật ký.
server.scanned: upsertsecurity_server_scanstheo(company_id, server_id)nếuseqlớn hơnlast_event_seqcủa dòng đó.- Ghi
security_finding_events(event_idunique),accepted.
- Trả 200
{"data": {"accepted": ["<event_id>", ...], "rejected": [{"event_id": "...", "reason": "unknown_server"}]}}.
Mã HTTP: 200 (kể cả khi có rejected), 401, 403 (token sai, thiếu quyền security.matcher), 422 (phong bì sai), 429 (rate limiter security-matcher, đề xuất 600 request mỗi phút), 5xx. Secmatch hiện xử lý: 2xx là đã giao (không thử lại sự kiện trong rejected), 429 và 5xx thử lại cả lô theo thứ tự, 4xx khác chuyển dead-letter. Vì vậy mọi lỗi tạm thời phía Access Hub (khóa, deadlock, DB không sẵn) phải trả 5xx, không trả 4xx.
POST /internal/v1/resync?company_id= của secmatch phát lại finding.opened cho mọi finding đang mở của công ty với seq mới: dùng để khôi phục sau sự cố. Finding Access Hub còn mở mà resync không phát lại thì giữ nguyên (P3 thêm đối soát).
11.5 SecmatchClient
Theo mẫu MonitoringCollectorClient: base URL và token admin riêng trong config (không trong DB), timeout kết nối 2 giây, đọc 5 giây, thử lại 2 lần với GET, ngắt mạch sau 5 lỗi liên tiếp trong 60 giây. Mọi lời gọi truyền company_id lấy từ Server hoặc phiên, không từ input người dùng.
| Dùng ở | Gọi | Phản hồi |
|---|---|---|
| Tab Bảo mật, danh sách gói của máy | GET /internal/v1/servers/{server_id}/packages?company_id=&q=&cursor=&limit= (limit tối đa 500) | {"data": {"items": [{server_id, name, version, arch, source_name, source_version, ecosystem}], "next_cursor"}}; 404 not_found khi máy chưa có inventory hoặc không thuộc công ty |
| Tìm máy theo gói | GET /internal/v1/companies/{company_id}/packages/search?name=&version_lt=&version_gte=&ecosystem=&cursor=&limit= | Cùng hình dạng, mỗi dòng một máy |
| Thẻ độ phủ trên trang tổng quan | GET /internal/v1/companies/{company_id}/summary | {"data": {servers, oldest_report_ms, ecosystems: {"ubuntu:24.04": 3, "release_eol": 1}, open_findings}} |
| Xóa máy (HUB-SEC-13) | DELETE /internal/v1/servers/{server_id}/data?company_id= | 204, idempotent |
| Purge công ty | DELETE /internal/v1/companies/{company_id}/data | 204, idempotent; secmatch giữ bia mộ để từ chối báo cáo nhận trước giờ xóa |
| Nút khôi phục cho quản trị (tùy chọn) | POST /internal/v1/resync?company_id= | 200 {"data": {"events": n}} |
Lỗi trả dạng {"error": {"code", "message"}}. Secmatch không sẵn sàng thì tab hiển thị "Không có dữ liệu", không lỗi 500.
11.6 Kiểm thử bắt buộc cho P1
- Lô có sự kiện của hai công ty và
server_idchéo: chỉ sự kiện đúng công ty được áp, sự kiện chéounknown_server. - Gửi lại cùng lô: kết quả giống hệt, không bản ghi trùng.
- Đảo thứ tự
opened(seq 5) vàresolved(seq 6): finding cuối cùngresolved. resolvedrồiopenedmới hơn: finding mở lại,resolved_atnull.- Tài khoản không có
security.matcher, tài khoản vai trò tùy chỉnh chứasecurity.matcher: 403. - Danh sách finding và tab máy chủ: người công ty A không thấy finding, máy, gói của B (gồm qua
SecmatchClientvớicompany_idcủa A vàserver_idcủa B: secmatch trả 404). - Xóa máy và purge công ty gọi secmatch sau commit, thử lại khi secmatch lỗi.
- Mẫu chạy thật
tests/ApiSamples/NN_security.phpgửi một lô thật từ fixture (dùngaccesshub-secmatchchạy cục bộ với DB thử, hoặc payload ghi sẵn trongtestdata/golden).