mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-08-18 13:17:08 +00:00
mini: resolve admin credentials from security.toml and env vars (#10021)
* mini: resolve admin credentials from security.toml and env vars weed mini started the admin UI without resolving admin.user/admin.password (and the read-only pair) from security.toml [admin] or WEED_ADMIN_* env vars, so the only way to protect the UI was the -admin.password flag. The standalone weed admin command applies these fallbacks in runAdmin via applyViperFallback; the mini path calls startAdminServer directly and skipped it, leaving authRequired false and the UI unauthenticated. * mini: load admin.toml maintenance settings The mini admin path runs ApplyMaintenanceConfigFromToml (via startAdminServer) against the global viper, but runMini never merged admin.toml, so file-based maintenance task settings ([maintenance.vacuum], .balance, .erasure_coding) were ignored under mini while the standalone weed admin honored them. Load it alongside master/volume config. * mini: support -admin.urlPrefix for the admin UI Expose the reverse-proxy subdirectory prefix that the standalone weed admin already supports, so the mini admin UI can run under e.g. /seaweedfs. The prefix is normalized the same way and passed through to startAdminServer.
This commit is contained in:
+25
-1
@@ -548,6 +548,7 @@ func initMiniAdminFlags() {
|
||||
miniAdminOptions.adminPassword = cmdMini.Flag.String("admin.password", "", "admin interface password (if empty, auth is disabled)")
|
||||
miniAdminOptions.readOnlyUser = cmdMini.Flag.String("admin.readOnlyUser", "", "read-only user username (optional, for view-only access)")
|
||||
miniAdminOptions.readOnlyPassword = cmdMini.Flag.String("admin.readOnlyPassword", "", "read-only user password (optional, for view-only access; requires admin.password to be set)")
|
||||
miniAdminOptions.urlPrefix = cmdMini.Flag.String("admin.urlPrefix", "", "URL path prefix when running the admin UI behind a reverse proxy under a subdirectory (e.g. /seaweedfs)")
|
||||
}
|
||||
|
||||
func init() {
|
||||
@@ -1164,6 +1165,7 @@ func runMini(cmd *Command, args []string) bool {
|
||||
util.LoadSecurityConfiguration()
|
||||
util.LoadConfiguration("master", false)
|
||||
util.LoadConfiguration("volume", false)
|
||||
util.LoadConfiguration("admin", false)
|
||||
miniOptions.v.applyDiskIOProbeConfig()
|
||||
|
||||
ensureMiniVolumeGrowthDefaults()
|
||||
@@ -1487,6 +1489,18 @@ func startS3Service() {
|
||||
miniS3Options.startS3Server()
|
||||
}
|
||||
|
||||
// applyMiniAdminCredentialFallback fills the admin credential flags from
|
||||
// security.toml [admin] / WEED_ADMIN_* env vars when they were not set on the
|
||||
// command line, mirroring the standalone `weed admin` command. CLI flags take
|
||||
// precedence. Note the read-only viper keys (admin.readonly.*) differ from the
|
||||
// mini flag names (admin.readOnly*).
|
||||
func applyMiniAdminCredentialFallback(options *AdminOptions) {
|
||||
applyViperFallback(cmdMini, options.adminUser, "admin.user", "admin.user")
|
||||
applyViperFallback(cmdMini, options.adminPassword, "admin.password", "admin.password")
|
||||
applyViperFallback(cmdMini, options.readOnlyUser, "admin.readOnlyUser", "admin.readonly.user")
|
||||
applyViperFallback(cmdMini, options.readOnlyPassword, "admin.readOnlyPassword", "admin.readonly.password")
|
||||
}
|
||||
|
||||
// startMiniAdminWithWorker starts the admin server with one worker
|
||||
func startMiniAdminWithWorker(allServicesReady chan struct{}) {
|
||||
defer close(allServicesReady) // Ensure channel is always closed on all paths
|
||||
@@ -1503,6 +1517,10 @@ func startMiniAdminWithWorker(allServicesReady chan struct{}) {
|
||||
// Set admin options
|
||||
*miniAdminOptions.master = masterAddr
|
||||
|
||||
// Resolve admin credentials from security.toml [admin] / WEED_ADMIN_* env
|
||||
// vars, matching the standalone `weed admin` command.
|
||||
applyMiniAdminCredentialFallback(&miniAdminOptions)
|
||||
|
||||
// Security validation: prevent empty username when password is set
|
||||
if *miniAdminOptions.adminPassword != "" && *miniAdminOptions.adminUser == "" {
|
||||
glog.Fatalf("Error: -admin.user cannot be empty when -admin.password is set")
|
||||
@@ -1532,6 +1550,12 @@ func startMiniAdminWithWorker(allServicesReady chan struct{}) {
|
||||
*miniAdminOptions.dataDir = filepath.Join(*miniDataFolders, "admin")
|
||||
}
|
||||
|
||||
// Normalize URL prefix the same way `weed admin` does.
|
||||
urlPrefix := strings.TrimRight(*miniAdminOptions.urlPrefix, "/")
|
||||
if urlPrefix != "" && !strings.HasPrefix(urlPrefix, "/") {
|
||||
urlPrefix = "/" + urlPrefix
|
||||
}
|
||||
|
||||
// Start admin server in background. trackMiniClient lets the Ctrl+C
|
||||
// handler wait for startAdminServer's graceful shutdown before filer/
|
||||
// volume/master tear down.
|
||||
@@ -1546,7 +1570,7 @@ func startMiniAdminWithWorker(allServicesReady chan struct{}) {
|
||||
if miniS3Options.portIceberg != nil {
|
||||
icebergPort = *miniS3Options.portIceberg
|
||||
}
|
||||
if err := startAdminServer(ctx, miniAdminOptions, *miniEnableAdminUI, icebergPort, ""); err != nil {
|
||||
if err := startAdminServer(ctx, miniAdminOptions, *miniEnableAdminUI, icebergPort, urlPrefix); err != nil {
|
||||
glog.Errorf("Admin server error: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package command
|
||||
|
||||
import "testing"
|
||||
|
||||
// weed mini must resolve admin credentials from security.toml [admin] /
|
||||
// WEED_ADMIN_* env vars the same way the standalone `weed admin` command does.
|
||||
// This exercises the production fallback so the flag-name -> viper-key mapping
|
||||
// stays correct, in particular the read-only keys where the mini flag
|
||||
// (admin.readOnlyUser) and viper key (admin.readonly.user) differ.
|
||||
func TestApplyMiniAdminCredentialFallbackFromEnv(t *testing.T) {
|
||||
adminUser, adminPassword, readOnlyUser, readOnlyPassword := "admin", "", "", ""
|
||||
options := &AdminOptions{
|
||||
adminUser: &adminUser,
|
||||
adminPassword: &adminPassword,
|
||||
readOnlyUser: &readOnlyUser,
|
||||
readOnlyPassword: &readOnlyPassword,
|
||||
}
|
||||
|
||||
t.Setenv("WEED_ADMIN_USER", "env-admin")
|
||||
t.Setenv("WEED_ADMIN_PASSWORD", "env-secret")
|
||||
t.Setenv("WEED_ADMIN_READONLY_USER", "env-ro")
|
||||
t.Setenv("WEED_ADMIN_READONLY_PASSWORD", "env-ro-secret")
|
||||
|
||||
applyMiniAdminCredentialFallback(options)
|
||||
|
||||
checks := []struct {
|
||||
name string
|
||||
got string
|
||||
want string
|
||||
}{
|
||||
{"adminUser", *options.adminUser, "env-admin"},
|
||||
{"adminPassword", *options.adminPassword, "env-secret"},
|
||||
{"readOnlyUser", *options.readOnlyUser, "env-ro"},
|
||||
{"readOnlyPassword", *options.readOnlyPassword, "env-ro-secret"},
|
||||
}
|
||||
for _, c := range checks {
|
||||
if c.got != c.want {
|
||||
t.Errorf("%s = %q, want %q", c.name, c.got, c.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user