Bind Feishu identity to OpenVPN sessions and refine management UI
This commit is contained in:
1 parent
347799cb1e
commit
8fd8c0cd73
27 files changed
+1556
-1011
No files matched your search
@@ -2,6 +2,7 @@ package openvpn
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@@ -52,6 +53,19 @@ func TestOIDCConfigAndSecrets(t *testing.T) {
|
||||
if stat, err := os.Stat(path); err != nil || stat.Mode().Perm() != 0o600 {
|
||||
t.Fatalf("auth configuration must be 0600: %v %v", stat, err)
|
||||
}
|
||||
pagePath := filepath.Join(service.InstanceDir(tunnel), "auth-result.html")
|
||||
page, err := os.ReadFile(pagePath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if stat, err := os.Stat(pagePath); err != nil || stat.Mode().Perm() != 0o600 {
|
||||
t.Fatalf("auth result template must be 0600: %v %v", stat, err)
|
||||
}
|
||||
for _, want := range []string{"name=\"viewport\"", "请返回 VPN 客户端", "请在 VPN 客户端查看连接状态"} {
|
||||
if !strings.Contains(string(page), want) {
|
||||
t.Fatalf("auth result template missing %q", want)
|
||||
}
|
||||
}
|
||||
passwordPath := filepath.Join(service.InstanceDir(tunnel), "management-password.txt")
|
||||
if stat, err := os.Stat(passwordPath); err != nil || stat.Mode().Perm() != 0o600 {
|
||||
t.Fatalf("management password must be 0600: %v %v", stat, err)
|
||||
@@ -79,6 +93,21 @@ func TestOIDCConfigAndSecrets(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuiltInOIDCUsesStableSubjectAsOpenVPNUsername(t *testing.T) {
|
||||
tunnel := &model.Tunnel{ID: 44, Protocol: "openvpn"}
|
||||
config := map[string]interface{}{
|
||||
"oidc_provider": "builtin", "oidc_issuer": "https://vpn.example.com/id",
|
||||
}
|
||||
generated := oidcConfig(tunnel, config)
|
||||
if !strings.Contains(generated, `openvpn-username: "string(token.claims.sub)"`) {
|
||||
t.Fatalf("built-in issuer must use verified subject as username: %s", generated)
|
||||
}
|
||||
config["oidc_provider"] = "external"
|
||||
if generated := oidcConfig(tunnel, config); strings.Contains(generated, "openvpn-username:") {
|
||||
t.Fatalf("external issuer username behavior must remain unchanged: %s", generated)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOIDCRejectsUnsafeURLs(t *testing.T) {
|
||||
base := map[string]interface{}{
|
||||
"auth_mode": "oidc", "oidc_issuer": "https://idp.example.com/realms/a",
|
||||
@@ -335,3 +364,85 @@ func TestSplitStatusFieldsSupportsOpenVPNStatusV3(t *testing.T) {
|
||||
t.Fatalf("status-version 3 fields parsed incorrectly: %#v", fields)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStatusUsernameDoesNotTreatUNDEFAsIdentity(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name, cn, username, want string
|
||||
}{
|
||||
{"password", "admin", "admin", "admin"},
|
||||
{"authenticated username", "UNDEF", "tenant:ou_123", "tenant:ou_123"},
|
||||
{"certificate", "client-cert", "UNDEF", "client-cert"},
|
||||
{"webauth without username", "UNDEF", "UNDEF", ""},
|
||||
{"webauth empty username", "UNDEF", "", ""},
|
||||
{"webauth lowercase placeholder", "undef", "UnDeF", ""},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
fields := splitStatusFields("CLIENT_LIST\t" + tc.cn + "\t203.0.113.1:1234\t10.8.0.2\t\t12\t34\t2026-10-22\t1234\t" + tc.username)
|
||||
if got := statusUsername(fields); got != tc.want {
|
||||
t.Fatalf("statusUsername() = %q, want %q", got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestWebAuthSessionIdentityAndTrafficTransition(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
t.Setenv("VPN_DATA_DIR", dir)
|
||||
previousDB := store.DB
|
||||
t.Cleanup(func() {
|
||||
if store.DB != nil {
|
||||
if sqlDB, err := store.DB.DB(); err == nil {
|
||||
_ = sqlDB.Close()
|
||||
}
|
||||
}
|
||||
store.DB = previousDB
|
||||
})
|
||||
if err := store.Init(dir); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
tunnel := &model.Tunnel{ID: 502, Name: "webauth-test", Protocol: "openvpn"}
|
||||
if err := store.DB.Create(tunnel).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, username := range []string{"UNDEF", "tenant:ou_123"} {
|
||||
if err := store.DB.Create(&model.VPNUser{TunnelID: tunnel.ID, Username: username, Enabled: true}).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
instanceDir := service.InstanceDir(tunnel)
|
||||
if err := os.MkdirAll(instanceDir, 0o700); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
writeStatus := func(username string, rx, tx int64) {
|
||||
t.Helper()
|
||||
line := fmt.Sprintf("CLIENT_LIST\tUNDEF\t203.0.113.1:1234\t10.8.0.2\t\t%d\t%d\t2026-10-22\t1234\t%s\n", rx, tx, username)
|
||||
if err := os.WriteFile(filepath.Join(instanceDir, "openvpn-status.log"), []byte(line), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
writeStatus("UNDEF", 150, 230)
|
||||
svc := New()
|
||||
if rx, tx := svc.collectTraffic(tunnel); rx != 150 || tx != 230 {
|
||||
t.Fatalf("initial traffic = %d/%d", rx, tx)
|
||||
}
|
||||
var unknown model.VPNSession
|
||||
if err := store.DB.Where("tunnel_id = ? AND status = ?", tunnel.ID, "active").First(&unknown).Error; err != nil || unknown.Username != "" || unknown.VPNUserID != 0 {
|
||||
t.Fatalf("unknown peer must not be attributed: %+v, %v", unknown, err)
|
||||
}
|
||||
var placeholder model.VPNUser
|
||||
store.DB.Where("tunnel_id = ? AND username = ?", tunnel.ID, "UNDEF").First(&placeholder)
|
||||
if placeholder.RxBytes != 0 || placeholder.TxBytes != 0 {
|
||||
t.Fatal("UNDEF placeholder received VPN user traffic")
|
||||
}
|
||||
if rx, tx := svc.collectTraffic(tunnel); rx != 0 || tx != 0 {
|
||||
t.Fatalf("unchanged snapshot counted twice: %d/%d", rx, tx)
|
||||
}
|
||||
writeStatus("tenant:ou_123", 160, 260)
|
||||
if rx, tx := svc.collectTraffic(tunnel); rx != 10 || tx != 30 {
|
||||
t.Fatalf("authenticated snapshot delta = %d/%d, want 10/30", rx, tx)
|
||||
}
|
||||
var identified model.VPNSession
|
||||
if err := store.DB.Where("tunnel_id = ? AND status = ?", tunnel.ID, "active").First(&identified).Error; err != nil || identified.Username != "tenant:ou_123" || identified.VPNUserID == 0 {
|
||||
t.Fatalf("verified username should link its own user: %+v, %v", identified, err)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user