mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-08-21 02:21:01 +00:00
feat(nodes): opt-in encryption at rest for the outbound node API token (#6186)
* node: encrypt outbound bearer token at rest * fix(nodes): keep bearer tokens encrypted throughout --------- Co-authored-by: n0ctal <293235942+n0ctal@users.noreply.github.com>
This commit is contained in:
@@ -15,6 +15,7 @@ import (
|
||||
_ "unsafe"
|
||||
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/config"
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/crypto/nodetoken"
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/database"
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/logger"
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/sub"
|
||||
@@ -35,6 +36,35 @@ import (
|
||||
// cannot accumulate admin-equivalent credentials that are never revoked.
|
||||
const cliFallbackTokenName = "cli-fallback"
|
||||
|
||||
// initNodeTokenCrypto loads the process codec, preferring the key file over
|
||||
// the environment and failing closed when an enabled policy lacks a key.
|
||||
func initNodeTokenCrypto() error {
|
||||
mode, err := nodetoken.ParseMode(config.GetNodeTokenEncryptionMode())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if mode == nodetoken.ModeOff {
|
||||
c, _ := nodetoken.NewCodec(nodetoken.ModeOff, nil)
|
||||
nodetoken.Init(c)
|
||||
return nil
|
||||
}
|
||||
ring, ferr := (nodetoken.FileKeySource{Path: config.GetNodeTokenKeyFile()}).Load()
|
||||
if ferr != nil {
|
||||
var eerr error
|
||||
if ring, eerr = (nodetoken.EnvKeySource{Var: config.GetNodeTokenKeyEnv()}).Load(); eerr != nil {
|
||||
return fmt.Errorf("load node-token key: file: %w; env: %w", ferr, eerr)
|
||||
}
|
||||
}
|
||||
c, err := nodetoken.NewCodec(mode, ring)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nodetoken.Init(c)
|
||||
// The CLI runs before package logger initialization, so use log.Printf.
|
||||
log.Printf("node-token encryption enabled (mode=%s, active-key=%s)", config.GetNodeTokenEncryptionMode(), c.ActiveKeyID())
|
||||
return nil
|
||||
}
|
||||
|
||||
// runWebServer initializes and starts the web server for the 3x-ui panel.
|
||||
func runWebServer() {
|
||||
log.Printf("Starting %v %v", config.GetName(), config.GetPanelVersion())
|
||||
@@ -69,6 +99,10 @@ func runWebServer() {
|
||||
}()
|
||||
}
|
||||
|
||||
if err := initNodeTokenCrypto(); err != nil {
|
||||
log.Fatalf("Error initializing node-token encryption: %v", err)
|
||||
}
|
||||
|
||||
err := database.InitDB(config.GetDBPath())
|
||||
if err != nil {
|
||||
log.Fatalf("Error initializing database: %v", err)
|
||||
@@ -303,6 +337,26 @@ func updateTgbotSetting(tgBotToken string, tgBotChatid string, tgBotRuntime stri
|
||||
}
|
||||
}
|
||||
|
||||
// encryptNodeTokens re-encrypts stored tokens after enablement or rotation.
|
||||
// It requires migration|required mode and a configured key.
|
||||
func encryptNodeTokens() {
|
||||
_ = godotenv.Load()
|
||||
if err := initNodeTokenCrypto(); err != nil {
|
||||
fmt.Println("node-token encryption init failed:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if err := database.InitDB(config.GetDBPath()); err != nil {
|
||||
fmt.Println("database initialization failed:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
changed, skipped, err := (&service.NodeService{}).MigrateNodeTokensToActiveKey()
|
||||
if err != nil {
|
||||
fmt.Println("token migration failed:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("node-token migration complete: %d re-encrypted, %d already current/skipped\n", changed, skipped)
|
||||
}
|
||||
|
||||
// updateSetting updates various panel settings including port, credentials, base path, listen IP, and two-factor authentication.
|
||||
func updateSetting(port int, username string, password string, webBasePath string, listenIP string, resetTwoFactor bool) error {
|
||||
err := database.InitDB(config.GetDBPath())
|
||||
@@ -573,12 +627,7 @@ func main() {
|
||||
oldUsage := flag.Usage
|
||||
flag.Usage = func() {
|
||||
oldUsage()
|
||||
fmt.Println()
|
||||
fmt.Println("Commands:")
|
||||
fmt.Println(" run run web panel")
|
||||
fmt.Println(" migrate migrate from other/old x-ui")
|
||||
fmt.Println(" migrate-db SQLite <-> .dump (--dump/--restore) or copy into PostgreSQL (--dsn)")
|
||||
fmt.Println(" setting set settings")
|
||||
fmt.Print(commandHelp())
|
||||
}
|
||||
|
||||
flag.Parse()
|
||||
@@ -597,6 +646,8 @@ func main() {
|
||||
runWebServer()
|
||||
case "migrate":
|
||||
migrateDb()
|
||||
case "encrypt-tokens":
|
||||
encryptNodeTokens()
|
||||
case "migrate-db":
|
||||
if err := migrateDbCmd.Parse(os.Args[2:]); err != nil {
|
||||
fmt.Println(err)
|
||||
@@ -686,3 +737,14 @@ func main() {
|
||||
settingCmd.Usage()
|
||||
}
|
||||
}
|
||||
|
||||
func commandHelp() string {
|
||||
return `
|
||||
Commands:
|
||||
run run web panel
|
||||
migrate migrate from other/old x-ui
|
||||
migrate-db SQLite <-> .dump (--dump/--restore) or copy into PostgreSQL (--dsn)
|
||||
encrypt-tokens encrypt node bearer tokens with the configured active key
|
||||
setting set settings
|
||||
`
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user