Add per-instance/user access whitelist

Features:
- New Instance.AccessMode: "open" (default) or "whitelist"
- New Instance.AllowNetworks + VPNUser.AllowNetworks: list of CIDRs
- Effective whitelist = instance allow_networks ∪ user allow_networks (dedup)
- Auto-generates client-connect.sh / client-disconnect.sh for OpenVPN:
    * Reads ccd/<cn> to extract CIDRs
    * Pushes "route <ip> <mask>" to client (client side)
    * Inserts iptables ACCEPT rules in FORWARD chain (server side, defense in depth)
    * Cleans up rules on disconnect
- server.conf auto-includes client-connect / client-disconnect directives
  and push "redirect-gateway def1 bypass-dhcp" in whitelist mode
- ccd/<cn> file format: first line ifconfig-push (static IP), then one CIDR per line
- Editing instance allow_networks refreshes all users' ccd automatically
- New PUT /api/instances/:id/users/:uid endpoint
- CIDR format validation; reject malformed inputs with friendly errors
- Dashboard shows whitelist_instances count and per-instance allow_networks table

Docs:
- README: new section "三、访问控制(白名单模式)" with usage, validation, pitfalls
- docs/API.md: updated Instance / VPNUser model + create/update payloads
- Renumbered client usage section as 四
This commit is contained in:
cnbugs
2026-08-09 20:47:21 +08:00
parent 77f8b59290
commit 4c8b7b5188
11 changed files with 697 additions and 64 deletions
+39 -25
View File
@@ -2,40 +2,54 @@ package model
import "time"
// AccessMode 控制客户端的访问范围。
// - "open": 不限制,客户端可访问所有可达网段(默认)
// - "whitelist":白名单模式,仅允许访问 Instance 与 User 合并后的 allow_networks
// 列表中的网段,其他内网流量在服务端 FORWARD 链被丢弃
type AccessMode string
const (
AccessOpen AccessMode = "open"
AccessWhitelist AccessMode = "whitelist"
)
// Instance 一个 OpenVPN 服务端实例。
// 每个实例使用独立端口与 PKI,运行在自己的 server.conf 下,
// 由 systemd 单元(或后台进程)托管,本服务通过 management 接口与之通信。
type Instance struct {
ID string `json:"id"`
Name string `json:"name"` // 唯一名,作为目录名
Port int `json:"port"` // openvpn 监听端口
Proto string `json:"proto"` // udp / tcp
Dev string `json:"dev"` // tun / tap
Subnet string `json:"subnet"` // 客户端子网,如 10.8.0.0/24
Cipher string `json:"cipher"` // 加密算法
AuthDigest string `json:"auth_digest"` // 摘要算法
PushDNS string `json:"push_dns"` // push "dhcp-option DNS x.x.x.x"
PushRoutes string `json:"push_routes"` // 多行
Extra string `json:"extra"` // 用户追加配置
Status string `json:"status"` // running/stopped/error
PID int `json:"pid"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
ID string `json:"id"`
Name string `json:"name"` // 唯一名,作为目录名
Port int `json:"port"` // openvpn 监听端口
Proto string `json:"proto"` // udp / tcp
Dev string `json:"dev"` // tun / tap
Subnet string `json:"subnet"` // 客户端子网,如 10.8.0.0/24
Cipher string `json:"cipher"` // 加密算法
AuthDigest string `json:"auth_digest"` // 摘要算法
PushDNS string `json:"push_dns"` // push "dhcp-option DNS x.x.x.x"
PushRoutes string `json:"push_routes"` // 多行
Extra string `json:"extra"` // 用户追加配置
AccessMode AccessMode `json:"access_mode"` // open | whitelist
AllowNetworks []string `json:"allow_networks"` // 实例级白名单 CIDR 列表
Status string `json:"status"` // running/stopped/error
PID int `json:"pid"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// VPNUser 一个 OpenVPN 客户端用户。
// 证书从对应实例的 PKI 中签发,可下载 .ovpn 客户端配置。
type VPNUser struct {
ID string `json:"id"`
InstanceID string `json:"instance_id"`
Username string `json:"username"` // 证书 CN
RealName string `json:"real_name"` // 备注
Email string `json:"email"`
Enabled bool `json:"enabled"` // 是否启用, false 即吊销/禁用
StaticIP string `json:"static_ip"` // ccd 固定地址, 空表示动态
CreatedAt time.Time `json:"created_at"`
ExpiresAt *time.Time `json:"expires_at,omitempty"`
RevokedAt *time.Time `json:"revoked_at,omitempty"`
ID string `json:"id"`
InstanceID string `json:"instance_id"`
Username string `json:"username"` // 证书 CN
RealName string `json:"real_name"` // 备注
Email string `json:"email"`
Enabled bool `json:"enabled"` // 是否启用, false 即吊销/禁用
StaticIP string `json:"static_ip"` // ccd 固定地址, 空表示动态
AllowNetworks []string `json:"allow_networks"` // 用户级白名单(在实例基础上叠加)
CreatedAt time.Time `json:"created_at"`
ExpiresAt *time.Time `json:"expires_at,omitempty"`
RevokedAt *time.Time `json:"revoked_at,omitempty"`
}
// AuditLog 操作审计日志