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.
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
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)"]
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
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)
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
Mặc định cô lập. Chia sẻ = ghi vpn_shares → sinh lại ACL → đẩy xuống mesh. Thu hồi thì xoá rule, push lại.
sequenceDiagram
autonumber
actor A as Chủ máy (User A)
participant DASH as Dashboard
participant API as authapi
participant PG as Postgres
participant HS as Headscale
participant B as User B
Note over A,B: Mặc định A và B không thấy máy của nhau
A->>DASH: Chia sẻ máy X cho email B, cổng 3389
DASH->>API: POST /v1/vpn/shares
API->>PG: B đã bật VPN? máy X thuộc A?
alt hợp lệ
API->>PG: INSERT vpn_shares
API->>HS: PUT /api/v1/policy (HuJSON)
HS->>B: đẩy ACL mới
Note over B,A: B RDP tới 100.64.0.x:3389 của A
else không hợp lệ
API-->>DASH: 400 / 404
end
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
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
}
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).
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.
| Cổng | Service | Public |
|---|---|---|
| 80 / 443 | nginx (TLS · vhost · proxy) | ✅ |
| 2222 | sish SSH (tạo tunnel) | ✅ |
| 20000–20049 | sish TCP public (pool 50) | ✅ |
| 3478/udp | Headscale STUN | ✅ |
| 53 tcp/udp | CoreDNS — DNS mặt tiền (ddns + forward acme + REFUSE) | ✅ |
| 8686 | SSH admin | ✅ (firewall) |
| 5353 / 5354 | acme-dns (TXT ACME) · authapi DDNS resolver | ❌ 127.0.0.1 |
| 8080 / 8089 / 8090 / 8091 | sish 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).
ARCHITECTURE.md trong repo triển khai. Tinh thần: minh bạch để cộng đồng tin tưởng.