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
This commit is contained in:
Ben McClelland
2026-02-21 11:35:01 -08:00
parent a81f9e5152
commit e7a1231e77
2 changed files with 139 additions and 5 deletions
+121 -5
View File
@@ -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".
+18
View File
@@ -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 #
#######################