fix: stabilize console scrolling and protect administrator login

This commit is contained in:
2026-09-30 00:09:01 +08:00
parent 9a15406f76
commit f56f233704
32 changed files with 1308 additions and 86 deletions

View File

@@ -10,17 +10,19 @@ import (
"strconv"
"time"
"git.yuntuo.hk/k2safe/orbit/internal/consoleauth"
"git.yuntuo.hk/k2safe/orbit/internal/retention"
)
type Config struct {
ConsoleApplicationAdmin string // ConsoleApplicationAdmin 可跨应用管理接入的唯一控制台登录名;空时禁用。
CredentialKeyFile string // CredentialKeyFile 管理员 Token 保管密钥文件路径;所有服务节点共用同一密钥环。
ArchiveRoot string // ArchiveRoot 私有持久归档卷绝对路径,空表示关闭后台执行器。
ArchiveStorageID core.ID // ArchiveStorageID 32位小写十六进制卷身份,各节点须挂载同一物理卷。
ArchiveMaxBytes int64 // ArchiveMaxBytes 全卷文件预算,默认10GiB。
ArchiveTickInterval time.Duration // ArchiveTickInterval 后台扫描间隔,默认10秒。
RetentionDefaults retention.Policy // RetentionDefaults 未保存数据库策略时使用的部署默认值。
ConsoleLogin consoleauth.LoginPolicy // ConsoleLogin 登录保护策略,所有服务节点使用一致配置。
ConsoleApplicationAdmin string // ConsoleApplicationAdmin 可跨应用管理接入的唯一控制台登录名;空时禁用。
CredentialKeyFile string // CredentialKeyFile 管理员 Token 保管密钥文件路径;所有服务节点共用同一密钥环。
ArchiveRoot string // ArchiveRoot 私有持久归档卷绝对路径,空表示关闭后台执行器。
ArchiveStorageID core.ID // ArchiveStorageID 32位小写十六进制卷身份,各节点须挂载同一物理卷。
ArchiveMaxBytes int64 // ArchiveMaxBytes 全卷文件预算,默认10GiB。
ArchiveTickInterval time.Duration // ArchiveTickInterval 后台扫描间隔,默认10秒。
RetentionDefaults retention.Policy // RetentionDefaults 未保存数据库策略时使用的部署默认值。
HTTPAddr string
MySQLDSN string
MaxOpen int
@@ -39,7 +41,12 @@ type Config struct {
func Load() (Config, error) { return FromEnv(os.Getenv) }
func FromEnv(get func(string) string) (Config, error) {
loginPolicy, err := loginPolicyFromEnv(get)
if err != nil {
return Config{}, err
}
c := Config{
ConsoleLogin: loginPolicy,
ConsoleApplicationAdmin: get("ORBIT_CONSOLE_APPLICATION_ADMIN"),
CredentialKeyFile: get("ORBIT_CREDENTIAL_KEY_FILE"),
ArchiveRoot: get("ORBIT_ARCHIVE_ROOT"), ArchiveStorageID: core.ID(get("ORBIT_ARCHIVE_STORAGE_ID")), ArchiveMaxBytes: 10 << 30, ArchiveTickInterval: 10 * time.Second,
@@ -154,7 +161,6 @@ func FromEnv(get func(string) string) (Config, error) {
}
c.ArchiveTickInterval = d
}
var err error
c.RetentionDefaults, err = retention.FromEnv(get)
if err != nil {
return Config{}, err

View File

@@ -0,0 +1,57 @@
package config
import (
"fmt"
"git.yuntuo.hk/k2safe/orbit/internal/consoleauth"
"net/netip"
"strconv"
"strings"
"time"
)
func loginPolicyFromEnv(get func(string) string) (consoleauth.LoginPolicy, error) {
p := consoleauth.DefaultLoginPolicy()
for _, f := range []struct {
name string
dst *int
}{
{"ORBIT_CONSOLE_LOGIN_FAILURE_LIMIT", &p.FailureLimit},
{"ORBIT_CONSOLE_LOGIN_ACCOUNT_LIMIT", &p.AccountLimit},
{"ORBIT_CONSOLE_LOGIN_IP_LIMIT", &p.IPLimit},
{"ORBIT_CONSOLE_LOGIN_AUDIT_DAYS", &p.AuditDays},
} {
if raw := get(f.name); raw != "" {
n, err := strconv.Atoi(raw)
if err != nil {
return p, fmt.Errorf("invalid %s", f.name)
}
*f.dst = n
}
}
for _, f := range []struct {
name string
dst *time.Duration
}{
{"ORBIT_CONSOLE_LOGIN_FAILURE_WINDOW", &p.FailureWindow},
{"ORBIT_CONSOLE_LOGIN_LOCK_DURATION", &p.LockDuration},
{"ORBIT_CONSOLE_LOGIN_RATE_WINDOW", &p.RateWindow},
} {
if raw := get(f.name); raw != "" {
d, err := time.ParseDuration(raw)
if err != nil {
return p, fmt.Errorf("invalid %s", f.name)
}
*f.dst = d
}
}
if raw := get("ORBIT_CONSOLE_TRUSTED_PROXIES"); raw != "" {
for _, item := range strings.Split(raw, ",") {
prefix, err := netip.ParsePrefix(strings.TrimSpace(item))
if err != nil {
return p, fmt.Errorf("invalid ORBIT_CONSOLE_TRUSTED_PROXIES CIDR")
}
p.TrustedProxies = append(p.TrustedProxies, prefix.Masked())
}
}
return p, p.Validate()
}

View File

@@ -0,0 +1,30 @@
package config
import (
"testing"
"time"
)
func TestConsoleLoginConfiguration(t *testing.T) {
p, err := loginPolicyFromEnv(func(string) string { return "" })
if err != nil || p.FailureLimit != 5 || p.LockDuration != 15*time.Minute || p.AuditDays != 90 {
t.Fatalf("defaults: %+v %v", p, err)
}
env := map[string]string{"ORBIT_CONSOLE_LOGIN_FAILURE_LIMIT": "3", "ORBIT_CONSOLE_LOGIN_LOCK_DURATION": "20m", "ORBIT_CONSOLE_TRUSTED_PROXIES": "127.0.0.1/32, ::1/128"}
p, err = loginPolicyFromEnv(func(k string) string { return env[k] })
if err != nil || p.FailureLimit != 3 || len(p.TrustedProxies) != 2 {
t.Fatalf("configured: %+v %v", p, err)
}
for k, v := range map[string]string{"ORBIT_CONSOLE_LOGIN_FAILURE_LIMIT": "0", "ORBIT_CONSOLE_LOGIN_ACCOUNT_LIMIT": "-1", "ORBIT_CONSOLE_LOGIN_IP_LIMIT": "10001", "ORBIT_CONSOLE_LOGIN_FAILURE_WINDOW": "0s", "ORBIT_CONSOLE_LOGIN_LOCK_DURATION": "25h", "ORBIT_CONSOLE_LOGIN_RATE_WINDOW": "bad", "ORBIT_CONSOLE_LOGIN_AUDIT_DAYS": "0", "ORBIT_CONSOLE_TRUSTED_PROXIES": "all"} {
t.Run(k, func(t *testing.T) {
if _, err := loginPolicyFromEnv(func(name string) string {
if name == k {
return v
}
return ""
}); err == nil {
t.Fatal("invalid policy accepted")
}
})
}
}