From e7a1231e77b08363d7a8aa2db51f721c900b3e01 Mon Sep 17 00:00:00 2001 From: Ben McClelland Date: Sat, 21 Feb 2026 11:26:01 -0800 Subject: [PATCH] feat: add cli options to specify webui gateway/admin listing New cli options added: webui-gateways - override auto-detected S3 gateway URLs for WebUI webui-admin-gateways - override auto-detected admin gateway URLs for WebUI These also accept env vars VGW_WEBUI_GATEWAYS and VGW_WEBUI_ADMIN_GATEWAYS for the options. When setting these, this will override the url auto-detection for the webui service urls dropdown options. By default, the gateway auto-detects URLs based on the configured port settings. Use these options to specify custom URLs when the auto-detected values are incorrect (e.g., when running behind a reverse proxy or load balancer). Multiple URLs can be specified with repeated options or a comma-separated list with the environment variables. for example: --webui-gateways https://s3.example.com \ --webui-gateways http://192.168.1.100:7070 or VGW_WEBUI_GATEWAYS=https://s3.example.com,http://192.168.1.100:7070 The gateway will validate the provided URLs with warnings for any invalid URL specified. The gateway will terminate if these options are set but contain no valid URLs. Also added sorting to the auto-detected URLs so that localhost URLs will be last in the list, since these will not likely work on remote systems. The specified lists when provided are left in the order they are specified to allow admins to determine dropdown list ordering. Fixes #1851 --- cmd/versitygw/main.go | 126 ++++++++++++++++++++++++++++++++++++++++-- extra/example.conf | 18 ++++++ 2 files changed, 139 insertions(+), 5 deletions(-) diff --git a/cmd/versitygw/main.go b/cmd/versitygw/main.go index dc8ffefb..bfcc1b22 100644 --- a/cmd/versitygw/main.go +++ b/cmd/versitygw/main.go @@ -21,6 +21,7 @@ import ( "net" "net/http" _ "net/http/pprof" + "net/url" "os" "strconv" "strings" @@ -97,6 +98,8 @@ var ( webuiPorts []string webuiCertFile, webuiKeyFile string webuiNoTLS bool + webuiGateways []string + webuiAdminGateways []string ) var ( @@ -150,6 +153,8 @@ documentation can be found in the GitHub wiki.`, ports = ctx.StringSlice("port") webuiPorts = ctx.StringSlice("webui") admPorts = ctx.StringSlice("admin-port") + webuiGateways = ctx.StringSlice("webui-gateways") + webuiAdminGateways = ctx.StringSlice("webui-admin-gateways") return nil }, Action: func(ctx *cli.Context) error { @@ -203,6 +208,16 @@ func initFlags() []cli.Flag { EnvVars: []string{"VGW_WEBUI_NO_TLS"}, Destination: &webuiNoTLS, }, + &cli.StringSliceFlag{ + Name: "webui-gateways", + Usage: "override auto-detected S3 gateway URLs for WebUI (e.g. 'http://localhost:7070', 'https://s3.example.com'; can be specified multiple times)", + EnvVars: []string{"VGW_WEBUI_GATEWAYS"}, + }, + &cli.StringSliceFlag{ + Name: "webui-admin-gateways", + Usage: "override auto-detected admin gateway URLs for WebUI (e.g. 'http://localhost:7080', 'https://admin.example.com'; can be specified multiple times)", + EnvVars: []string{"VGW_WEBUI_ADMIN_GATEWAYS"}, + }, &cli.StringFlag{ Name: "access", Usage: "root user access key", @@ -1023,16 +1038,37 @@ func runGateway(ctx context.Context, be backend.Backend) error { } var gateways []string - for _, p := range ports { - urls, err := buildServiceURLs(p, sslEnabled) + if len(webuiGateways) > 0 { + // Use explicitly provided gateway URLs if specified + // Validate explicitly provided URLs + validGateways, err := validateGatewayURLs(webuiGateways, "webui gateway") if err != nil { - return fmt.Errorf("webui: build gateway URLs: %w", err) + return err } - gateways = append(gateways, urls...) + gateways = validGateways + } else { + // Auto-detect from configured ports + for _, p := range ports { + urls, err := buildServiceURLs(p, sslEnabled) + if err != nil { + return fmt.Errorf("webui: build gateway URLs: %w", err) + } + gateways = append(gateways, urls...) + } + // Sort so localhost/127.0.0.1 URLs appear last + sortGatewayURLs(gateways) } adminGateways := gateways - if len(admPorts) > 0 { + if len(webuiAdminGateways) > 0 { + // Validate explicitly provided admin gateway URLs + validAdminGateways, err := validateGatewayURLs(webuiAdminGateways, "webui admin gateway") + if err != nil { + return err + } + adminGateways = validAdminGateways + } else if len(admPorts) > 0 { + // Auto-detect from configured admin ports adminGateways = nil for _, admPort := range admPorts { urls, err := buildServiceURLs(admPort, admSSLEnabled) @@ -1041,6 +1077,8 @@ func runGateway(ctx context.Context, be backend.Backend) error { } adminGateways = append(adminGateways, urls...) } + // Sort so localhost/127.0.0.1 URLs appear last + sortGatewayURLs(adminGateways) } if quiet { @@ -1462,6 +1500,84 @@ func buildServiceURLs(spec string, ssl bool) ([]string, error) { return urls, nil } +// isLocalhost checks if a URL contains a localhost address +func isLocalhost(url string) bool { + return strings.Contains(url, "localhost") || + strings.Contains(url, "127.0.0.1") || + strings.Contains(url, "[::1]") +} + +// validateGatewayURLs validates a list of gateway URLs and returns only valid ones. +// It prints warnings for invalid URLs and returns an error if the input list is non-empty +// but contains no valid URLs after filtering. +func validateGatewayURLs(urls []string, urlType string) ([]string, error) { + if len(urls) == 0 { + return urls, nil + } + + var validURLs []string + for _, urlStr := range urls { + // Skip empty strings + if strings.TrimSpace(urlStr) == "" { + continue + } + + parsedURL, err := url.Parse(urlStr) + if err != nil { + fmt.Fprintf(os.Stderr, "WARNING: invalid %s URL %q: %v\n", urlType, urlStr, err) + continue + } + + // Ensure the URL has a scheme (http or https) + if parsedURL.Scheme == "" { + fmt.Fprintf(os.Stderr, "WARNING: invalid %s URL %q: missing scheme (must be http:// or https://)\n", urlType, urlStr) + continue + } + + if parsedURL.Scheme != "http" && parsedURL.Scheme != "https" { + fmt.Fprintf(os.Stderr, "WARNING: invalid %s URL %q: unsupported scheme %q (must be http or https)\n", urlType, urlStr, parsedURL.Scheme) + continue + } + + // Ensure the URL has a host + if parsedURL.Host == "" { + fmt.Fprintf(os.Stderr, "WARNING: invalid %s URL %q: missing host\n", urlType, urlStr) + continue + } + + validURLs = append(validURLs, urlStr) + } + + if len(validURLs) == 0 { + return nil, fmt.Errorf("%s URLs specified but none are valid", urlType) + } + + return validURLs, nil +} + +// sortGatewayURLs sorts a list of URLs so that localhost and 127.0.0.1 URLs appear last +func sortGatewayURLs(urls []string) { + if len(urls) <= 1 { + return + } + + // Partition URLs into two groups: non-localhost and localhost + var nonLocal []string + var local []string + + for _, url := range urls { + if isLocalhost(url) { + local = append(local, url) + } else { + nonLocal = append(nonLocal, url) + } + } + + // Rebuild the slice with non-localhost first, then localhost + copy(urls, nonLocal) + copy(urls[len(nonLocal):], local) +} + // validatePortConflicts checks for port conflicts across s3 api, admin, and webui ports. // A bare port spec (e.g., ":7071") binds to all interfaces and will conflict with any other // binding on the same port, whether it's ":7071" or "ip:7071". diff --git a/extra/example.conf b/extra/example.conf index 0251d9d3..1e077770 100644 --- a/extra/example.conf +++ b/extra/example.conf @@ -245,6 +245,24 @@ ROOT_SECRET_ACCESS_KEY= # 'https://webui.example.com') to improve security. #VGW_CORS_ALLOW_ORIGIN= +# The VGW_WEBUI_GATEWAYS option allows you to override the auto-detected S3 +# gateway URLs that are provided to the Web GUI. By default, the gateway +# auto-detects URLs based on the configured VGW_PORT settings. Use this option +# to specify custom URLs when the auto-detected values are incorrect (e.g., when +# running behind a reverse proxy or load balancer). Multiple URLs can be +# specified as a comma-separated list. +# Example: VGW_WEBUI_GATEWAYS=https://s3.example.com,http://192.168.1.100:7070 +#VGW_WEBUI_GATEWAYS= + +# The VGW_WEBUI_ADMIN_GATEWAYS option allows you to override the auto-detected +# admin gateway URLs that are provided to the Web GUI. By default, the gateway +# auto-detects URLs based on the configured VGW_ADMIN_PORT settings (or uses the +# same URLs as VGW_WEBUI_GATEWAYS if no admin ports are configured). Use this +# option to specify custom admin URLs when the auto-detected values are incorrect. +# Multiple URLs can be specified as a comma-separated list. +# Example: VGW_WEBUI_ADMIN_GATEWAYS=https://admin.example.com,http://192.168.1.100:7080 +#VGW_WEBUI_ADMIN_GATEWAYS= + ####################### # Debug / Diagnostics # #######################