Files
tailscale-custom/.agent/uploads/1773926322631_audit_log_review.md
T
huanld 2fb067ecbf
checklocks / checklocks (push) Has been cancelled
CodeQL / Analyze (go) (push) Has been cancelled
natlab-integrationtest / natlab-integrationtest (push) Has been cancelled
CI / gomod-cache (push) Has been cancelled
CI / race-root-integration (1/4) (push) Has been cancelled
CI / race-root-integration (2/4) (push) Has been cancelled
CI / race-root-integration (3/4) (push) Has been cancelled
CI / race-root-integration (4/4) (push) Has been cancelled
CI / test (-race, amd64, 1/3) (push) Has been cancelled
CI / test (-race, amd64, 2/3) (push) Has been cancelled
CI / test (-race, amd64, 3/3) (push) Has been cancelled
CI / test (386) (push) Has been cancelled
CI / test (amd64) (push) Has been cancelled
CI / Windows (benchmarks) (push) Has been cancelled
CI / Windows (1/2) (push) Has been cancelled
CI / Windows (2/2) (push) Has been cancelled
CI / macos (push) Has been cancelled
CI / privileged (push) Has been cancelled
CI / vm (push) Has been cancelled
CI / cross (386, linux) (push) Has been cancelled
CI / cross (amd64, darwin) (push) Has been cancelled
CI / cross (amd64, freebsd) (push) Has been cancelled
CI / cross (amd64, openbsd) (push) Has been cancelled
CI / cross (amd64, windows) (push) Has been cancelled
CI / cross (arm, 5, linux) (push) Has been cancelled
CI / cross (arm, 7, linux) (push) Has been cancelled
CI / cross (arm64, darwin) (push) Has been cancelled
CI / cross (arm64, linux) (push) Has been cancelled
CI / cross (arm64, windows) (push) Has been cancelled
CI / cross (loong64, linux) (push) Has been cancelled
CI / ios (push) Has been cancelled
CI / crossmin (amd64, illumos) (push) Has been cancelled
CI / crossmin (amd64, plan9) (push) Has been cancelled
CI / crossmin (amd64, solaris) (push) Has been cancelled
CI / crossmin (ppc64, aix) (push) Has been cancelled
CI / android (push) Has been cancelled
CI / wasm (push) Has been cancelled
CI / tailscale_go (push) Has been cancelled
CI / fuzz (push) Has been cancelled
CI / depaware (push) Has been cancelled
CI / go_generate (push) Has been cancelled
CI / make_tidy (push) Has been cancelled
CI / licenses (push) Has been cancelled
CI / staticcheck (macOS) (push) Has been cancelled
CI / staticcheck (Linux) (push) Has been cancelled
CI / staticcheck (Windows) (push) Has been cancelled
CI / staticcheck (Portable (1/4)) (push) Has been cancelled
CI / staticcheck (Portable (2/4)) (push) Has been cancelled
CI / staticcheck (Portable (3/4)) (push) Has been cancelled
CI / staticcheck (Portable (4/4)) (push) Has been cancelled
CI / notify_slack (push) Has been cancelled
CI / merge_blocker (push) Has been cancelled
CI / check_mergeability_strict (push) Has been cancelled
CI / check_mergeability (push) Has been cancelled
Dockerfile build / deploy (push) Has been cancelled
test installer.sh / test (curl, alpine:3.21) (push) Has been cancelled
test installer.sh / test (curl, alpine:edge) (push) Has been cancelled
test installer.sh / test (curl, alpine:latest) (push) Has been cancelled
test installer.sh / test (curl, amazonlinux:latest) (push) Has been cancelled
test installer.sh / test (curl, archlinux:latest) (push) Has been cancelled
test installer.sh / test (curl, debian:oldstable-slim) (push) Has been cancelled
test installer.sh / test (curl, debian:sid-slim) (push) Has been cancelled
test installer.sh / test (curl, debian:stable-slim, 1.80.0) (push) Has been cancelled
test installer.sh / test (curl, debian:testing-slim) (push) Has been cancelled
test installer.sh / test (curl, elementary/docker:stable) (push) Has been cancelled
test installer.sh / test (curl, elementary/docker:unstable) (push) Has been cancelled
test installer.sh / test (curl, fedora:latest, 1.80.0) (push) Has been cancelled
test installer.sh / test (curl, kalilinux/kali-dev) (push) Has been cancelled
test installer.sh / test (curl, kalilinux/kali-rolling) (push) Has been cancelled
test installer.sh / test (curl, opensuse/leap:latest) (push) Has been cancelled
test installer.sh / test (curl, opensuse/tumbleweed:latest) (push) Has been cancelled
test installer.sh / test (curl, oraclelinux:8) (push) Has been cancelled
test installer.sh / test (curl, oraclelinux:9) (push) Has been cancelled
test installer.sh / test (curl, parrotsec/core:latest) (push) Has been cancelled
test installer.sh / test (curl, rockylinux:8.7) (push) Has been cancelled
test installer.sh / test (curl, rockylinux:9) (push) Has been cancelled
test installer.sh / test (curl, ubuntu:20.04) (push) Has been cancelled
test installer.sh / test (curl, ubuntu:22.04) (push) Has been cancelled
test installer.sh / test (curl, ubuntu:24.04, 1.80.0) (push) Has been cancelled
test installer.sh / test (wget, debian:oldstable-slim) (push) Has been cancelled
test installer.sh / test (wget, debian:sid-slim) (push) Has been cancelled
update-flake / update-flake (push) Has been cancelled
tailscale.com/cmd/vet / vet (push) Has been cancelled
test installer.sh / notify-slack (push) Has been cancelled
feat: security hardening, production roadmap, admin panel v1
Client security fixes (cmd/tailscale-tray/main.go):
- SSRF protection in Add Server dialog (validateControlURL): reject
  private/loopback/link-local/cloud-metadata IPs via DNS resolution
- RCE gate on AuthURL/BrowseToURL exec paths (validateAuthURL)
- Sanitized URL logging (sanitizeURLForLog drops query auth tokens)
- Error handling on exec.Command with user-facing showError()

Admin panel security (web-admin):
- Bcrypt password hashing (replaces SHA256)
- Rate limiting: 5 failed logins → 15-min lockout
- Session + login attempt cleanup goroutine (hourly)
- url.QueryEscape / encodeURIComponent for all API params
- Fail-hard startup when no TLS and non-loopback bind
- ADMIN_PASSWORD required (no default), password min 12 chars
- Username regex whitelist

Installer hardening (Setup.wxs):
- util:PermissionEx restricts SCM access: only Administrators +
  SYSTEM can start/stop/reconfigure service. Authenticated Users
  limited to QueryStatus/QueryConfig/Interrogate
- Vital="yes" on ServiceInstall

Docs & roadmap:
- PRODUCTION_ROADMAP.md: 5-milestone plan (security + features +
  distribution + ops) with granular tasks, effort, done-when
- CLIENT_SECURITY_AUDIT.md, SECURITY_FIXES.md, DEPLOYMENT.md
- AI assistant rules (.cursorrules, .antigravityrules, etc.)

Build & distribution:
- build-msi.ps1, deploy-and-sign.ps1, sign-release.ps1
- redeploy.ps1, tray-deploy.ps1, test-msi.ps1
- installer/msi/ alternative WXS setup
- Restored .github/workflows/ removed in mirror cleanup

.gitignore hardened: *.pfx, *.p12, *.key, *.pem, .env*
2026-04-22 15:18:11 +07:00

13 KiB

🔍 Multi-Agent Review: Audit Log Flow

Mục đích: Phản biện luồng Audit Log từ 4 góc nhìn chuyên gia, đảm bảo triển khai được cho cả API-onlyBlazor Server model.


🤖 Agent 1: @backend-specialist — Architecture & Scalability

Điểm mạnh

Aspect Assessment
Layered Architecture Controller → Service → Repository pattern tuân thủ tốt
Async by default Tất cả publisher/consumer đều async Task
Dependency Injection Lifetimes đúng: Scoped (per-request), Singleton (stateless), HostedService (background)
Error Isolation Audit log failure swallow exceptions, không break business flow
Message Broker Decoupling RabbitMQ tách biệt hoàn toàn publisher/consumer

🔴 CRITICAL: Gaps khi triển khai cho Blazor Server

Caution

IRequestContextAccessor KHÔNG hoạt động trong Blazor Server vì không có HTTP request pipeline!

Problem Blazor WASM (hiện tại) Blazor Server API-only
IRequestContextAccessor HTTP POST → Middleware populate SignalR Hub, không có HttpContext HTTP Request
CorrelationIdMiddleware HTTP headers Không có HTTP headers HTTP headers
LogService POST to API HttpClient call ⚠️ Gọi API từ server → chính nó? Circular! N/A
INS_AuditLogger lấy auth AuthenticationStateProvider Works (nhưng flow khác) N/A

Giải pháp đề xuất cho Blazor Server:

// 1. Tạo ICircuitContextAccessor thay vì IRequestContextAccessor
public interface ICircuitContextAccessor
{
    string? UserId { get; set; }
    string? Username { get; set; }
    string? IpAddress { get; set; }
    string? SessionId { get; set; }
    string? CorrelationId { get; set; }
}

// 2. Populate từ CircuitHandler thay vì middleware
public class AuditCircuitHandler : CircuitHandler
{
    public override Task OnCircuitOpenedAsync(Circuit circuit, CancellationToken ct)
    {
        // Lấy HttpContext từ initial connection
        // HttpContext chỉ available tại thời điểm kết nối SignalR
    }
}

// 3. Inject trực tiếp IAuditLogPublisher trong Blazor Server
// Không cần roundtrip qua HTTP/gRPC
@inject IAuditLogPublisher AuditPublisher

🟡 WARNING: Missing Pieces cho API-only Model

  1. Không có Logging Controller trong tài liệu: Module Backend Controller nhận POST từ LogService nhưng KHÔNG được document. Cần document /api/logging/audit endpoint ở module backend.

  2. Thiếu Retry Policy cho Publisher: AuditLogPublisher.PublishAsync() catch exception nhưng KHÔNG retry. Nếu RabbitMQ tạm unavailable → message bị mất.

// Đề xuất: Thêm local buffer hoặc retry policy
public class ResilientAuditLogPublisher : IAuditLogPublisher
{
    // Option A: Polly retry
    // Option B: In-memory queue + background flush
    // Option C: Outbox pattern (ghi vào DB trước, background job publish)
}
  1. Consumer chỉ xử lý AuditLogMessage: Nhưng LoggingGrpcService publish RabbitMQAuditLogDto (khác model). Consumer cần handle cả 2 hoặc cần adapter thống nhất model trước khi publish.

🤖 Agent 2: @security-auditor — Security & OWASP

🔴 CRITICAL Findings

# Severity Finding OWASP Remediation
S1 CRITICAL RollbackSqlGenerator build SQL bằng string concat A05 Injection Rollback SQL chỉ dùng cho reference, KHÔNG BAO GIỜ auto-execute. Thêm warning comment.
S2 HIGH RequestBody được log raw A09 Logging Nhạy cảm data (passwords, tokens) có thể bị log. Cần sanitize/redact sensitive fields trước khi publish.
S3 HIGH Không có authorization trên Consumer A01 Access Control HostedService chạy với full system permissions. Cần validate message source.
S4 MEDIUM OldData/NewData lưu raw JSON A04 Crypto PII data (email, phone, address) lưu plaintext. Cần xem xét encryption at rest hoặc redaction policy.
S5 MEDIUM Không có rate limiting cho audit publish A02 Misconfiguration Malicious module có thể flood RabbitMQ queue. Cần throttle hoặc quota per module.

Chi tiết Finding S1: SQL Injection Risk

// HIỆN TẠI: RollbackSqlGenerator.cs
private string FormatValue(JsonElement element)
{
    return element.ValueKind switch
    {
        JsonValueKind.String => $"N'{EscapeSqlString(element.GetString() ?? "")}'",
        // EscapeSqlString chỉ replace ' → '' 
        // KHÔNG đủ cho tất cả SQL injection vectors!
    };
}

Warning

EscapeSqlString chỉ escape single quotes. Các vectors khác như ; DROP TABLE trong numeric fields, hoặc Unicode tricks vẫn có thể exploit nếu rollback SQL được auto-execute.

Remediation:

  • Rollback SQL chỉ dùng tham khảo (display-only) → Thêm -- WARNING: Manual review required before execution
  • Nếu cần auto-execute → chuyển sang parameterized queries hoặc stored procedure

Chi tiết Finding S2: Sensitive Data Logging

// AuditLogPublisher.cs line 123
RequestBody = _requestContext.RequestBody,  // ← RAW body!

Đề xuất: Thêm redaction middleware:

public static class SensitiveDataRedactor
{
    private static readonly string[] SensitiveFields = 
        { "password", "token", "secret", "creditCard", "ssn" };
    
    public static string Redact(string? json) { /* redact matching fields */ }
}

🤖 Agent 3: @debugger — Failure Modes & Observability

Failure Mode Analysis (5 Whys)

Scenario: Audit log KHÔNG được ghi vào DB

WHY message không vào DB?
→ Consumer NACK (return false) hoặc message bị mất

WHY consumer NACK?
→ DbContext.SaveChangesAsync() throw exception (DB connection fail, constraint violation)

WHY DbContext fail?
→ Connection string sai, hoặc schema [log] chưa tạo, hoặc DB full

WHY không biết bị lỗi?
→ LogError chỉ log local, KHÔNG có alerting mechanism

WHY không có alerting?
→ CHƯA implement Dead Letter Queue (DLQ) hoặc health check cho consumer

🟡 Missing Observability

Gap Impact Recommendation
Không có DLQ NACK message bị requeue vô hạn → CPU spike Thêm DLQ: ins.server.auditlog.dlq sau N retries
Không có metrics Không biết queue depth, processing rate, failure rate Export Prometheus metrics từ consumer
Consumer một chiều Không biết consumer chạy hay không từ UI Đã có /api/rabbitmq/consumer-status nhưng cần health check dashboard
Message ordering Không guarantee order → UPDATE có thể đến trước INSERT Chấp nhận được vì audit log timestamp-based, nhưng cần document
Duplicate messages RabbitMQ at-least-once delivery → có thể duplicate INSERT Cần idempotency check (correlation_id + timestamp + table + rowId)

Troubleshooting Gaps trong tài liệu

Tài liệu thiếu các scenarios debugging phổ biến:

1. "Consumer connected nhưng không process message"
   → Kiểm tra binding key mismatch (logs.audit.server vs logs.audit.#)
   → Kiểm tra exchange type (topic vs direct)

2. "Message bị mất sau restart"
   → Queue có durable=true không?
   → Message có persistent=true không?
   → Kiểm tra IRabbitMQService default settings

3. "Blazor Server không ghi audit log"
   → IRequestContextAccessor null (không có HTTP context)
   → LogService gọi HTTP tới chính nó (circular dependency)

🤖 Agent 4: @frontend-specialist — Blazor Component & UX

Blazor Server vs WASM: Component Behavior

Aspect Blazor WASM Blazor Server Action Required
LogService HTTP call Client → Server (OK) Server → Server ( Circular) Inject IAuditLogPublisher trực tiếp
INS_AuditLogger rendering Component trên client Component trên server Hoạt động, nhưng UserId flow khác
AuthenticationStateProvider Frontend auth state Server auth state Cả hai đều hoạt động
Error swallowing User không thấy lỗi User không thấy lỗi Consistent
Serialization overhead JSON serialize trên client JSON serialize trên server Cả hai đều OK

🔴 CRITICAL: LogService Circular Call trong Blazor Server

Blazor Server:
  INS_AuditLogger → LogService.AuditAsync() 
    → HttpClient.PostAsync("/api/logging/audit")
    → Gọi tới CHÍNH SERVER ĐANG CHẠY
    → Self-referencing HTTP call → có thể deadlock hoặc connection exhaustion!

Giải pháp:

// Tạo IServerAuditLogger cho Blazor Server
public interface IServerAuditLogger
{
    Task LogAsync(string tableName, string operation, string? rowId, 
                  string? oldData, string? newData);
}

// Implementation inject trực tiếp IAuditLogPublisher (không qua HTTP)
public class ServerAuditLogger : IServerAuditLogger
{
    private readonly IAuditLogPublisher _publisher;
    private readonly AuthenticationStateProvider _authState;

    public async Task LogAsync(string tableName, string operation, 
        string? rowId, string? oldData, string? newData)
    {
        var user = await GetCurrentUser();
        await _publisher.PublishAsync("dbo", tableName, rowId ?? "", 
            operation, oldData, newData, functionName: "BlazorServer");
    }
}

Component Registration Pattern

// Blazor WASM → dùng LogService (HTTP) 
services.AddScoped<LogService>();

// Blazor Server → dùng ServerAuditLogger (direct inject)
services.AddScoped<IServerAuditLogger, ServerAuditLogger>();

// Shared → INS_AuditLogger cần abstraction
// Option: ILogServiceAbstraction với 2 implementations

📋 Tổng hợp: Bảng Action Items

Priority Finding Agent Model Action
🔴 P0 IRequestContextAccessor không hoạt động Blazor Server Backend Blazor Server Tạo ICircuitContextAccessor + CircuitHandler
🔴 P0 LogService circular call trong Blazor Server Frontend Blazor Server Tạo IServerAuditLogger inject trực tiếp IAuditLogPublisher
🔴 P0 SQL Injection risk trong rollback SQL Security Cả hai Mark rollback SQL là display-only, thêm warning
🟡 P1 Sensitive data logging (passwords, tokens) Security Cả hai Thêm redaction trước khi publish
🟡 P1 Không có DLQ cho failed messages Debugger Cả hai Configure DLQ + max retry count
🟡 P1 Message model mismatch (DTO vs full model) Backend Cả hai Thống nhất hoặc adapter pattern
🟡 P1 Thiếu module logging controller doc Backend API Document /api/logging/audit endpoint
🟢 P2 Không có duplicate detection Debugger Cả hai Idempotency check trên consumer
🟢 P2 Thiếu metrics/observability Debugger Cả hai Prometheus metrics cho queue depth
🟢 P2 Không có rate limiting cho publish Security API Throttle per-module publish rate

🏗️ Đề xuất: Abstraction Layer cho Multi-Model Support

Để audit log hoạt động trên cả API, Blazor WASM, và Blazor Server:

graph TD
    subgraph "Blazor WASM"
        A["INS_AuditLogger"] --> B["LogService<br/>(HTTP Client)"]
        B --> C["Module Controller<br/>POST /api/logging/audit"]
        C --> D["gRPC → LoggingGrpcService"]
    end

    subgraph "Blazor Server"
        E["INS_AuditLogger"] --> F["IServerAuditLogger<br/>(Direct Inject)"]
        F --> G["IAuditLogPublisher<br/>(Direct)"]
    end

    subgraph "API Only"
        H["Controller Code"] --> G
    end

    D --> I["IRabbitMQService.SendAsync()"]
    G --> I
    I --> J["RabbitMQ<br/>ins.logs.exchange"]
    J --> K["AuditLogConsumerService"]
    K --> L["SQL Server<br/>[log].[audit_log]"]

Key abstraction: IAuditService interface cho cả 3 model

public interface IAuditService
{
    Task LogAsync(string tableName, string operation, string? rowId,
                  object? oldData, object? newData, string? functionName = null);
}

// Blazor WASM implementation → HTTP → gRPC → RabbitMQ
public class WasmAuditService : IAuditService { }

// Blazor Server implementation → Direct → RabbitMQ
public class ServerAuditService : IAuditService { }

// API implementation → Direct → RabbitMQ  
public class ApiAuditService : IAuditService { }

Registration per hosting model:

// Program.cs
if (isBlazorServer)
    services.AddScoped<IAuditService, ServerAuditService>();
else if (isBlazorWasm)
    services.AddScoped<IAuditService, WasmAuditService>();
else // API
    services.AddScoped<IAuditService, ApiAuditService>();