Trang chủ › Kiến trúc
⚙️ Minh bạch kỹ thuật

Kiến trúc entry.io.vn

Toàn bộ hệ thống được trình bày công khai — từ kiến trúc tổng thể tới luồng xác thực, mạng riêng, và mô hình dữ liệu. Vì chúng tôi tin minh bạch là cốt lõi.

1. Kiến trúc tổng thể

nginx tuyến đầu (TLS, định tuyến, rate-limit). Các thành phần chạy trong Docker trên một VPS.

graph TB
  subgraph clients["Người dùng"]
    DEV["Dev (ssh -R)"]
    BROWSER["Trình duyệt"]
    TSC["Tailscale client"]
    RESOLV["Resolver công cộng"]
  end
  DNS["DNS @ TenTen<br/>ddns → NS CoreDNS"]
  subgraph vps["VPS entry.io.vn — Docker"]
    NGINX["nginx :443<br/>TLS · rate-limit · gzip · http2<br/>+ conf.d giới hạn IP"]
    WWW["/var/www tĩnh"]
    SISH["sish :2222 / :8080 / :20000-20049"]
    AUTH["authapi :8090<br/>+ DDNS resolver :5354"]
    PG[("Postgres 16")]
    HS["headscale :8091 · DERP · STUN"]
    COREDNS["CoreDNS :53 (DNS mặt tiền)"]
    ACME["acme-dns :5353"]
  end
  BROWSER -->|HTTPS| NGINX
  DEV -->|SSH :2222| SISH
  TSC -->|WireGuard / DERP| HS
  RESOLV -->|":53"| COREDNS
  NGINX -->|"*.entry.io.vn"| SISH
  NGINX -->|tĩnh| WWW
  NGINX -->|/v1 /internal| AUTH
  NGINX -->|vpn.entry.io.vn| HS
  SISH -->|webhook auth| AUTH
  AUTH --> PG
  AUTH -->|REST| HS
  AUTH -->|poll clients| SISH
  COREDNS -.->|"auth → :5353"| ACME
  COREDNS -.->|"ddns → :5354"| AUTH

2. Định tuyến — nginx phân luồng theo Host

graph LR
  REQ["HTTPS :443"] --> H{"Host?"}
  H -->|entry.io.vn| S1["/var/www/entry"]
  H -->|app.entry.io.vn| AP{"path"}
  AP -->|/v1 /internal| AUTH["authapi"]
  AP -->|còn lại| S2["dashboard SPA"]
  H -->|admin.entry.io.vn| AD{"path"}
  AD -->|/v1/admin| AUTH
  AD -->|còn lại| S3["admin SPA"]
  H -->|vpn.entry.io.vn| HS["headscale"]
  H -->|"*.entry.io.vn"| SISH["sish (tunnel)"]

3. Tạo tunnel + xác thực

sish gọi webhook authapi để kiểm tra SSH key & quota. Reconciler đồng bộ trạng thái tunnel.

sequenceDiagram
  autonumber
  participant DEV as Dev
  participant S as sish :2222
  participant A as authapi
  participant PG as Postgres
  DEV->>S: ssh -R sub:80:localhost:3000 (pubkey)
  S->>A: POST /internal/auth/key {fingerprint}
  A->>PG: lookup ssh_keys + users (idx)
  A->>PG: COUNT tunnels active (quota)
  alt hợp lệ và dưới hạn gói
    A-->>S: 200 allow
    S-->>DEV: https://sub.entry.io.vn
  else từ chối
    A-->>S: 403 reason
    S-->>DEV: Connection closed
  end
  loop reconciler
    A->>S: GET /_sish/api/clients
    A->>PG: ingest vào bảng tunnels
  end

4. Luồng request HTTP của người xem

Người xem → nginx (TLS) → sish → qua đường hầm ngược về máy dev.

sequenceDiagram
  autonumber
  participant V as Người xem
  participant N as nginx :443
  participant S as sish :8080
  participant D as Dev localhost:3000
  V->>N: GET https://myapp.entry.io.vn
  N->>N: TLS (cert wildcard) + rate-limit
  N->>S: proxy theo Host header
  S->>D: qua đường hầm SSH ngược (-R)
  D-->>S: HTTP response
  S-->>N: response
  N-->>V: HTTPS (gzip + http2)

5. Mạng riêng (VPN) — Headscale mesh

Máy nối thẳng P2P khi được; sau CGNAT thì relay qua DERP. Mã hoá WireGuard đầu-cuối.

graph TB
  subgraph mesh["100.64.0.0/10 (WireGuard)"]
    MA["Máy A · 100.64.0.2"]
    MB["Máy B · 100.64.0.3"]
  end
  HS["Headscale<br/>control + ACL"]
  DERP["DERP relay (Hà Nội)"]
  AUTH["authapi"]
  MA -. preauthkey .-> HS
  HS -->|đẩy ACL + DERP map| MA
  MA <-->|P2P trực tiếp| MB
  MA <-->|relay khi CGNAT| DERP
  DERP <--> MB
  AUTH -->|"REST: user / node / key / policy"| HS

7. Alias SSH + ProxyJump

Alias định tuyến theo tên (qua cổng 2222). Riêng tư qua tcp-aliases-allowed-users. Hai lớp xác thực độc lập.

sequenceDiagram
  autonumber
  actor OWN as Máy đích (owner)
  participant S as sish :2222
  participant API as authapi
  actor CLI as Máy client
  Note over OWN,S: 1. Tạo alias riêng tư
  OWN->>S: ssh -R myssh:22:localhost:22 ... allowed-users=FP_client
  S->>API: webhook auth (owner)
  API-->>S: allow
  S->>S: alias myssh, allowed = [FP_owner, FP_client]
  Note over CLI,S: 2. Client vào qua ProxyJump
  CLI->>S: ssh -J entry.io.vn:2222 user@myssh (key client)
  S->>API: webhook auth (client)
  API-->>S: allow
  S->>S: FP_client có trong allowed?
  alt được phép
    S->>OWN: forward qua đường hầm tới localhost:22
    OWN-->>CLI: kênh tới sshd máy đích
    Note over CLI,OWN: 3. sshd máy đích xác thực key client
  else không trong allowed-users
    S-->>CLI: từ chối
  end

8. Mô hình dữ liệu

erDiagram
  users ||--o{ ssh_keys : has
  users ||--o{ tunnels : owns
  users ||--o{ reserved_subdomains : reserves
  users ||--o{ vpn_devices : has
  users ||--o{ vpn_shares : grants
  users ||--o{ alias_shares : owns
  users ||--o{ ddns_hosts : owns
  users ||--o{ tunnel_acl : restricts
  users {
    bigint id PK
    citext email
    text plan
    text headscale_user
  }
  ssh_keys {
    bigint id PK
    bigint user_id FK
    text fingerprint
  }
  tunnels {
    bigint id PK
    bigint user_id FK
    text subdomain
    text kind
    timestamptz ended_at
  }
  vpn_shares {
    bigint id PK
    bigint owner_id FK
    bigint grantee_id FK
    text ports
  }
  alias_shares {
    bigint id PK
    bigint owner_id FK
    citext grantee_email
  }
  ddns_hosts {
    bigint id PK
    citext label UK
    inet ipv4
  }
  tunnel_acl {
    bigint id PK
    text subdomain UK
    text cidrs
    bool enabled
  }

9. DNS & DDNS — CoreDNS mặt tiền

CoreDNS chiếm :53, route theo zone + REFUSE phần lạ (không open-resolver). acme-dns lùi nội bộ :5353; authapi nhúng DDNS resolver :5354 (đọc Postgres).

graph TB
  R["Resolver công cộng"] --> CD["CoreDNS :53<br/>mặt tiền · bind IP public"]
  CD -->|ddns.entry.io.vn| DR["authapi resolver :5354<br/>dns2 đọc Postgres"]
  CD -->|"auth.* ACME"| AD["acme-dns :5353"]
  CD -->|domain lạ| REF["REFUSED"]
  ROUTER["Router / cron user"] -->|"/nic/update DynDNS"| NG["nginx → authapi"]
  NG -->|ghi IP| PG[("ddns_hosts")]
  DR -. đọc .-> PG
  NFT["nftables RRL UDP/53"] -. bảo vệ .-> CD

Tên <label>.ddns.entry.io.vn + token → router gọi /nic/update → authapi ghi IP → CoreDNS forward query về resolver dns2 → trả A/AAAA (TTL 60).

10. Giới hạn IP cho tunnel (allowlist)

Opt-in từng subdomain cố định: chỉ IP/CIDR trong danh sách mới vào được. Sinh nginx server block riêng → tunnel thường 0 overhead, không SPOF.

sequenceDiagram
  autonumber
  actor U as User (Dashboard)
  participant API as authapi
  participant SYNC as acl-sync (cron host)
  participant NG as nginx
  participant V as Visitor
  U->>API: PUT /v1/tunnel-acl (cidrs)
  API->>API: validCidr + kiểm sở hữu reserved
  loop mỗi phút
    SYNC->>API: GET /internal/nginx-acl
    API-->>SYNC: server block allow cidr, deny all
    SYNC->>NG: nginx -t rồi reload (rollback nếu lỗi)
  end
  V->>NG: GET sub.entry.io.vn
  alt IP trong allowlist
    NG->>V: proxy sang sish
  else IP ngoài
    NG-->>V: 403 (chặn tại biên)
  end

server_name khớp chính xác → ưu tiên hơn wildcard. Đổi rule hiệu lực ~1 phút. nginx -t trước reload, lỗi thì rollback → không bao giờ sập.

11. Hạ tầng cổng / network

CổngServicePublic
80 / 443nginx (TLS · vhost · proxy)
2222sish SSH (tạo tunnel)
20000–20049sish TCP public (pool 50)
3478/udpHeadscale STUN
53 tcp/udpCoreDNS — DNS mặt tiền (ddns + forward acme + REFUSE)
8686SSH admin✅ (firewall)
5353 / 5354acme-dns (TXT ACME) · authapi DDNS resolver❌ 127.0.0.1
8080 / 8089 / 8090 / 8091sish http · acme-dns · authapi · headscale — nội bộ❌ 127.0.0.1

DERP/control của VPN đi qua vpn.entry.io.vn:443 (nginx → headscale).

🔍 Mã nguồn cấu hình & sơ đồ đầy đủ ở ARCHITECTURE.md trong repo triển khai. Tinh thần: minh bạch để cộng đồng tin tưởng.