fix: stabilize console scrolling and protect administrator login
This commit is contained in:
@@ -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
|
||||
|
||||
57
internal/config/console_login.go
Normal file
57
internal/config/console_login.go
Normal 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()
|
||||
}
|
||||
30
internal/config/console_login_test.go
Normal file
30
internal/config/console_login_test.go
Normal 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")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user