mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-10-07 06:47:51 +02:00
Secure Weed Mini Admin Listeners by Default (#11613)
* securing admin Signed-off-by: Subhadeep Maity <smaity@slb.com> * updated readme Signed-off-by: Subhadeep Maity <322813880+deepnemesis@users.noreply.github.com> * fixed pr comments Signed-off-by: Subhadeep Maity <322813880+deepnemesis@users.noreply.github.com> * docs: tidy weed mini admin bind notes Drop the new single-entry CHANGELOG.md since changes are documented via GitHub releases, and rewrap the README paragraph to match the surrounding one-line style without self-referential issue/PR links. * review comments Signed-off-by: Subhadeep Maity <322813880+deepnemesis@users.noreply.github.com> --------- Signed-off-by: Subhadeep Maity <smaity@slb.com> Signed-off-by: Subhadeep Maity <322813880+deepnemesis@users.noreply.github.com> Co-authored-by: Subhadeep Maity <smaity@slb.com> Co-authored-by: Chris Lu <chris.lu@gmail.com>
This commit is contained in:
3 files changed
+325
-17
No files matched your search
@@ -82,6 +82,8 @@ AWS_ACCESS_KEY_ID=admin AWS_SECRET_ACCESS_KEY=secret \
|
||||
|
||||
The same process also runs the master, a volume server, the filer, WebDAV, the Iceberg REST catalog, and the Admin UI. Add `S3_TABLE_BUCKET=warehouse` to also create an Iceberg table bucket, or `warehouse:LANCE` for a Lance one. Drop the AWS keys to run without authentication for development.
|
||||
|
||||
Without Admin authentication or mTLS, `weed mini` binds the Admin UI/API and its worker gRPC control plane to loopback rather than `-ip.bind`. Set `WEED_ADMIN_PASSWORD` or configure `https.admin` mTLS to keep the network bind. Remote workers must opt in with `-admin.worker.ip=<address>` and should configure `grpc.admin` mTLS. `-admin.allowInsecureBind` restores the legacy unauthenticated network bind and should only be used on an isolated network.
|
||||
|
||||
> macOS: if the binary is quarantined, run `xattr -d com.apple.quarantine ./weed` first.
|
||||
|
||||
`weed mini` is auto-tuned for one node and is fine for single-node production, such as an S3 gateway that issues presigned URLs. See [Quick Start with weed mini][WeedMini].
|
||||
|
||||
+107
-16
@@ -69,6 +69,7 @@ var (
|
||||
miniEnableWebDAV *bool
|
||||
miniEnableS3 *bool
|
||||
miniEnableAdminUI *bool
|
||||
miniAdminWorkerBindIP *string
|
||||
miniS3IamReadOnly *bool
|
||||
miniVolumeMaxDataVolumeCounts *string
|
||||
// MiniClusterCtx is the context for the mini cluster. If set, the mini cluster will stop when the context is cancelled.
|
||||
@@ -567,6 +568,8 @@ 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.allowInsecureBind = cmdMini.Flag.Bool("admin.allowInsecureBind", false, "INSECURE: allow the unauthenticated admin interface to bind to -ip.bind instead of loopback")
|
||||
miniAdminWorkerBindIP = cmdMini.Flag.String("admin.worker.ip", "127.0.0.1", "worker gRPC bind address; expose only with grpc.admin mTLS configured")
|
||||
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)")
|
||||
}
|
||||
|
||||
@@ -957,7 +960,11 @@ func ensureAllPortsAvailableOnIP(bindIp string) error {
|
||||
// first: an in-process rerun would otherwise inherit the closed listener
|
||||
// of the previous run and only find out inside Serve.
|
||||
miniAdminOptions.workerGrpcListener = nil
|
||||
if listener, err := net.Listen("tcp", util.JoinHostPort(bindIp, *miniAdminOptions.grpcPort)); err != nil {
|
||||
workerGrpcBindIP := miniAdminOptions.workerGrpcBindIp
|
||||
if workerGrpcBindIP == "" {
|
||||
workerGrpcBindIP = "127.0.0.1"
|
||||
}
|
||||
if listener, err := listenMiniAdminWorker(workerGrpcBindIP, *miniAdminOptions.grpcPort); err != nil {
|
||||
glog.Warningf("Could not reserve Admin gRPC port %d: %v", *miniAdminOptions.grpcPort, err)
|
||||
} else {
|
||||
miniAdminOptions.workerGrpcListener = listener
|
||||
@@ -984,6 +991,12 @@ func ensureAllPortsAvailableOnIP(bindIp string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// listenMiniAdminWorker reserves the exact worker gRPC address that the Admin
|
||||
// server will later use, preventing another connection from taking the port.
|
||||
func listenMiniAdminWorker(bindIP string, port int) (net.Listener, error) {
|
||||
return net.Listen("tcp", util.JoinHostPort(bindIP, port))
|
||||
}
|
||||
|
||||
// initializeGrpcPortsOnIP initializes all gRPC ports based on their HTTP ports on a specific IP
|
||||
// If a gRPC port is 0, it will be set to httpPort + GrpcPortOffset
|
||||
// This must be called after HTTP ports are finalized and before services start
|
||||
@@ -1239,6 +1252,10 @@ func runMini(cmd *Command, args []string) bool {
|
||||
|
||||
// Determine bind IP
|
||||
bindIp := getBindIp()
|
||||
miniAdminOptions.workerGrpcBindIp = *miniAdminWorkerBindIP
|
||||
if miniAdminOptions.workerGrpcBindIp == "" {
|
||||
miniAdminOptions.workerGrpcBindIp = "127.0.0.1"
|
||||
}
|
||||
|
||||
// Ensure all ports are available, find alternatives if needed
|
||||
if err := ensureAllPortsAvailableOnIP(bindIp); err != nil {
|
||||
@@ -1583,6 +1600,46 @@ func applyMiniAdminCredentialFallback(options *AdminOptions) {
|
||||
applyViperFallback(cmdMini, options.readOnlyPassword, "admin.readOnlyPassword", "admin.readonly.password")
|
||||
}
|
||||
|
||||
// miniAdminBindIP selects a loopback HTTP bind unless Admin authentication,
|
||||
// mTLS, or an explicit insecure opt-out permits the requested address.
|
||||
func miniAdminBindIP(requestedIP string, passwordConfigured, mtlsConfigured, allowInsecure bool) string {
|
||||
if isLoopbackIp(requestedIP) || passwordConfigured || mtlsConfigured || allowInsecure {
|
||||
return requestedIP
|
||||
}
|
||||
return "127.0.0.1"
|
||||
}
|
||||
|
||||
// miniAdminWorkerAddress encodes the finalized HTTP and gRPC ports in the
|
||||
// server-address format understood by pb.ServerToGrpcAddress.
|
||||
func miniAdminWorkerAddress(bindIP string, httpPort, grpcPort int) string {
|
||||
return fmt.Sprintf("%s:%d.%d", miniAdminWorkerDialIP(bindIP), httpPort, grpcPort)
|
||||
}
|
||||
|
||||
// miniAdminWorkerDialIP maps unspecified listener addresses to same-family
|
||||
// loopback destinations while preserving specific addresses for local dials.
|
||||
func miniAdminWorkerDialIP(bindIP string) string {
|
||||
ip := net.ParseIP(strings.TrimSuffix(strings.TrimPrefix(bindIP, "["), "]"))
|
||||
if ip == nil || !ip.IsUnspecified() {
|
||||
return bindIP
|
||||
}
|
||||
if ip.To4() != nil {
|
||||
return "127.0.0.1"
|
||||
}
|
||||
return "::1"
|
||||
}
|
||||
|
||||
// miniAdminAdvertisedIP returns a reachable address for URLs in the welcome
|
||||
// message while preserving a specifically selected Admin HTTP bind address.
|
||||
func miniAdminAdvertisedIP() string {
|
||||
if miniAdminOptions.ip == nil || *miniAdminOptions.ip == "" {
|
||||
return *miniIp
|
||||
}
|
||||
if *miniAdminOptions.ip == "0.0.0.0" || *miniAdminOptions.ip == "::" {
|
||||
return *miniIp
|
||||
}
|
||||
return *miniAdminOptions.ip
|
||||
}
|
||||
|
||||
// startMiniAdminWithWorker starts the admin server with one worker
|
||||
func startMiniAdminWithWorker(allServicesReady chan struct{}) {
|
||||
defer close(allServicesReady) // Ensure channel is always closed on all paths
|
||||
@@ -1590,9 +1647,6 @@ func startMiniAdminWithWorker(allServicesReady chan struct{}) {
|
||||
// Admin shuts down when mini clients shutdown is triggered.
|
||||
ctx := miniClientsCtx()
|
||||
|
||||
// Determine bind IP for health checks
|
||||
bindIp := getBindIp()
|
||||
|
||||
// Prepare master address with gRPC port
|
||||
masterAddr := string(pb.NewServerAddress(*miniIp, *miniMasterOptions.port, *miniMasterOptions.portGrpc))
|
||||
|
||||
@@ -1611,6 +1665,34 @@ func startMiniAdminWithWorker(allServicesReady chan struct{}) {
|
||||
// vars, matching the standalone `weed admin` command.
|
||||
applyMiniAdminCredentialFallback(&miniAdminOptions)
|
||||
|
||||
requestedBindIP := getBindIp()
|
||||
hasMTLS := util.GetViper().GetString("https.admin.key") != "" &&
|
||||
util.GetViper().GetString("https.admin.ca") != ""
|
||||
allowInsecure := miniAdminOptions.allowInsecureBind != nil &&
|
||||
*miniAdminOptions.allowInsecureBind
|
||||
bindIP := miniAdminBindIP(
|
||||
requestedBindIP,
|
||||
*miniAdminOptions.adminPassword != "",
|
||||
hasMTLS,
|
||||
allowInsecure,
|
||||
)
|
||||
miniAdminOptions.ip = &bindIP
|
||||
if bindIP != requestedBindIP {
|
||||
glog.Warningf(
|
||||
"Admin authentication is disabled; binding the admin interface to %s instead of %s. "+
|
||||
"Set -admin.password, configure https.admin mTLS, or use "+
|
||||
"-admin.allowInsecureBind to retain the insecure network bind.",
|
||||
bindIP,
|
||||
requestedBindIP,
|
||||
)
|
||||
} else if allowInsecure && !isLoopbackIp(bindIP) &&
|
||||
*miniAdminOptions.adminPassword == "" && !hasMTLS {
|
||||
glog.Warningf(
|
||||
"-admin.allowInsecureBind exposes the unauthenticated admin interface on %s.",
|
||||
bindIP,
|
||||
)
|
||||
}
|
||||
|
||||
// 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")
|
||||
@@ -1681,7 +1763,7 @@ func startMiniAdminWithWorker(allServicesReady chan struct{}) {
|
||||
}()
|
||||
|
||||
// Wait for admin server's HTTP port to be ready before launching worker
|
||||
adminAddr := "http://" + util.JoinHostPort(bindIp, *miniAdminOptions.port)
|
||||
adminAddr := "http://" + util.JoinHostPort(bindIP, *miniAdminOptions.port)
|
||||
if err := waitForAdminServerReady(ctx, adminAddr); err != nil {
|
||||
// If the parent context was cancelled (e.g. a previous in-process
|
||||
// mini run is being torn down), bail out gracefully instead of
|
||||
@@ -1705,7 +1787,10 @@ func startMiniAdminWithWorker(allServicesReady chan struct{}) {
|
||||
startMiniPluginWorker(ctx, workerDir)
|
||||
|
||||
// Wait for worker to be ready by polling its gRPC port
|
||||
workerGrpcAddr := fmt.Sprintf("%s:%d", bindIp, *miniAdminOptions.grpcPort)
|
||||
workerGrpcAddr := util.JoinHostPort(
|
||||
miniAdminWorkerDialIP(miniAdminOptions.workerGrpcBindIp),
|
||||
*miniAdminOptions.grpcPort,
|
||||
)
|
||||
waitForWorkerReady(workerGrpcAddr)
|
||||
if miniProgressBoard != nil {
|
||||
miniProgressBoard.ready("Admin")
|
||||
@@ -1783,7 +1868,11 @@ func waitForWorkerReady(workerGrpcAddr string) {
|
||||
func startMiniWorker(workerDir string) {
|
||||
glog.V(1).Infof("Initializing standard worker runtime")
|
||||
|
||||
adminAddr := fmt.Sprintf("%s:%d", *miniIp, *miniAdminOptions.port)
|
||||
adminAddr := miniAdminWorkerAddress(
|
||||
miniAdminOptions.workerGrpcBindIp,
|
||||
*miniAdminOptions.port,
|
||||
*miniAdminOptions.grpcPort,
|
||||
)
|
||||
capabilities := "vacuum,ec,balance"
|
||||
|
||||
// Use common worker directory
|
||||
@@ -1858,11 +1947,11 @@ func startMiniWorker(workerDir string) {
|
||||
func startMiniPluginWorker(ctx context.Context, workerDir string) {
|
||||
glog.V(1).Infof("Starting plugin worker for admin server")
|
||||
|
||||
adminAddr := fmt.Sprintf("%s:%d", *miniIp, *miniAdminOptions.port)
|
||||
resolvedAdminAddr := resolvePluginWorkerAdminServer(adminAddr)
|
||||
if resolvedAdminAddr != adminAddr {
|
||||
glog.V(1).Infof("Resolved mini plugin worker admin endpoint: %s -> %s", adminAddr, resolvedAdminAddr)
|
||||
}
|
||||
adminAddr := miniAdminWorkerAddress(
|
||||
miniAdminOptions.workerGrpcBindIp,
|
||||
*miniAdminOptions.port,
|
||||
*miniAdminOptions.grpcPort,
|
||||
)
|
||||
|
||||
// Use common worker directory
|
||||
|
||||
@@ -1880,7 +1969,7 @@ func startMiniPluginWorker(ctx context.Context, workerDir string) {
|
||||
}
|
||||
|
||||
pluginRuntime, err := pluginworker.NewWorker(pluginworker.WorkerOptions{
|
||||
AdminServer: resolvedAdminAddr,
|
||||
AdminServer: adminAddr,
|
||||
WorkerID: workerID,
|
||||
WorkerVersion: version.Version(),
|
||||
WorkerAddress: *miniIp,
|
||||
@@ -1919,13 +2008,15 @@ const credentialsInstructionTemplate = `
|
||||
Creates initial credentials for the 'mini' user and pre-creates the bucket.
|
||||
|
||||
Option 2: Use the Admin UI
|
||||
Open: http://%s:%d
|
||||
Open: http://%s
|
||||
Add a new identity to create S3 credentials.
|
||||
`
|
||||
|
||||
// printWelcomeMessage prints the welcome message after all services are running
|
||||
func printWelcomeMessage() {
|
||||
var sb strings.Builder
|
||||
adminIP := miniAdminAdvertisedIP()
|
||||
adminHTTPAddress := util.JoinHostPort(adminIP, *miniAdminOptions.port)
|
||||
|
||||
sb.WriteString("╔═══════════════════════════════════════════════════════════════════════════════╗\n")
|
||||
sb.WriteString("║ SeaweedFS Mini - All-in-One Mode ║\n")
|
||||
@@ -1947,7 +2038,7 @@ func printWelcomeMessage() {
|
||||
}
|
||||
}
|
||||
if *miniEnableAdminUI {
|
||||
fmt.Fprintf(&sb, " Admin UI: http://%s:%d\n", *miniIp, *miniAdminOptions.port)
|
||||
fmt.Fprintf(&sb, " Admin UI: http://%s\n", adminHTTPAddress)
|
||||
}
|
||||
|
||||
fmt.Fprintf(&sb, "\n Data Directory: %s\n", *miniDataFolders)
|
||||
@@ -1970,7 +2061,7 @@ func printWelcomeMessage() {
|
||||
// run, configured via env vars, static config file, etc.) — no need
|
||||
// to show setup hints.
|
||||
case *miniEnableAdminUI:
|
||||
fmt.Fprintf(&sb, credentialsInstructionTemplate, *miniIp, *miniAdminOptions.port)
|
||||
fmt.Fprintf(&sb, credentialsInstructionTemplate, adminHTTPAddress)
|
||||
default:
|
||||
sb.WriteString("\n To create S3 credentials, use environment variables:\n\n")
|
||||
sb.WriteString(" export AWS_ACCESS_KEY_ID=your-access-key\n")
|
||||
|
||||
@@ -1,12 +1,19 @@
|
||||
package command
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/seaweedfs/seaweedfs/weed/pb"
|
||||
)
|
||||
|
||||
// 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.
|
||||
// TestApplyMiniAdminCredentialFallbackFromEnv verifies those environment
|
||||
// fallbacks without starting the full mini cluster.
|
||||
func TestApplyMiniAdminCredentialFallbackFromEnv(t *testing.T) {
|
||||
adminUser, adminPassword, readOnlyUser, readOnlyPassword := "admin", "", "", ""
|
||||
options := &AdminOptions{
|
||||
@@ -39,3 +46,211 @@ func TestApplyMiniAdminCredentialFallbackFromEnv(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestMiniAdminBindIP covers the authentication-dependent HTTP bind policy.
|
||||
func TestMiniAdminBindIP(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
requestedIP string
|
||||
passwordConfigured bool
|
||||
mtlsConfigured bool
|
||||
allowInsecure bool
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "unauthenticated wildcard binds to loopback",
|
||||
requestedIP: "0.0.0.0",
|
||||
want: "127.0.0.1",
|
||||
},
|
||||
{
|
||||
name: "unauthenticated IPv6 wildcard binds to loopback",
|
||||
requestedIP: "::",
|
||||
want: "127.0.0.1",
|
||||
},
|
||||
{
|
||||
name: "existing loopback bind is preserved",
|
||||
requestedIP: "127.0.0.1",
|
||||
want: "127.0.0.1",
|
||||
},
|
||||
{
|
||||
name: "password permits requested bind",
|
||||
requestedIP: "0.0.0.0",
|
||||
passwordConfigured: true,
|
||||
want: "0.0.0.0",
|
||||
},
|
||||
{
|
||||
name: "mTLS permits requested bind",
|
||||
requestedIP: "0.0.0.0",
|
||||
mtlsConfigured: true,
|
||||
want: "0.0.0.0",
|
||||
},
|
||||
{
|
||||
name: "explicit insecure opt-out permits requested bind",
|
||||
requestedIP: "0.0.0.0",
|
||||
allowInsecure: true,
|
||||
want: "0.0.0.0",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := miniAdminBindIP(
|
||||
tt.requestedIP,
|
||||
tt.passwordConfigured,
|
||||
tt.mtlsConfigured,
|
||||
tt.allowInsecure,
|
||||
)
|
||||
if got != tt.want {
|
||||
t.Fatalf("miniAdminBindIP() = %q, want %q", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestMiniAdminWorkerBindDefaultsToLoopback protects the worker control-plane default.
|
||||
func TestMiniAdminWorkerBindDefaultsToLoopback(t *testing.T) {
|
||||
if miniAdminWorkerBindIP == nil {
|
||||
t.Fatal("mini Admin worker bind flag is not initialized")
|
||||
}
|
||||
if got, want := *miniAdminWorkerBindIP, "127.0.0.1"; got != want {
|
||||
t.Fatalf("default mini Admin worker bind IP = %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestListenMiniAdminWorkerUsesRequestedAddress verifies the production
|
||||
// listener is actually restricted to the configured loopback address.
|
||||
func TestListenMiniAdminWorkerUsesRequestedAddress(t *testing.T) {
|
||||
listener, err := listenMiniAdminWorker("127.0.0.1", 0)
|
||||
if err != nil {
|
||||
t.Fatalf("listen for mini Admin worker: %v", err)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
_ = listener.Close()
|
||||
})
|
||||
|
||||
address, ok := listener.Addr().(*net.TCPAddr)
|
||||
if !ok {
|
||||
t.Fatalf("listener address type = %T, want *net.TCPAddr", listener.Addr())
|
||||
}
|
||||
if !address.IP.IsLoopback() {
|
||||
t.Fatalf("listener IP = %s, want loopback", address.IP)
|
||||
}
|
||||
}
|
||||
|
||||
// TestMiniAdminWorkerAddressUsesFinalGrpcPort verifies that local workers do
|
||||
// not have to discover or infer a custom Admin worker gRPC port.
|
||||
func TestMiniAdminWorkerAddressUsesFinalGrpcPort(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
ip string
|
||||
want string
|
||||
wantGrpc string
|
||||
}{
|
||||
{
|
||||
name: "IPv4",
|
||||
ip: "127.0.0.1",
|
||||
want: "127.0.0.1:23646.34567",
|
||||
wantGrpc: "127.0.0.1:34567",
|
||||
},
|
||||
{
|
||||
name: "IPv6",
|
||||
ip: "::1",
|
||||
want: "::1:23646.34567",
|
||||
wantGrpc: "[::1]:34567",
|
||||
},
|
||||
{
|
||||
name: "IPv4 wildcard dials loopback",
|
||||
ip: "0.0.0.0",
|
||||
want: "127.0.0.1:23646.34567",
|
||||
wantGrpc: "127.0.0.1:34567",
|
||||
},
|
||||
{
|
||||
name: "IPv6 wildcard dials loopback",
|
||||
ip: "::",
|
||||
want: "::1:23646.34567",
|
||||
wantGrpc: "[::1]:34567",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
address := miniAdminWorkerAddress(tt.ip, 23646, 34567)
|
||||
if address != tt.want {
|
||||
t.Fatalf("miniAdminWorkerAddress() = %q, want %q", address, tt.want)
|
||||
}
|
||||
if got := pb.ServerToGrpcAddress(address); got != tt.wantGrpc {
|
||||
t.Fatalf("pb.ServerToGrpcAddress() = %q, want %q", got, tt.wantGrpc)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestMiniAdminWorkerDialIP verifies wildcard listener addresses are converted
|
||||
// into valid same-family destinations without rewriting specific addresses.
|
||||
func TestMiniAdminWorkerDialIP(t *testing.T) {
|
||||
tests := []struct {
|
||||
bindIP string
|
||||
want string
|
||||
}{
|
||||
{bindIP: "0.0.0.0", want: "127.0.0.1"},
|
||||
{bindIP: "::", want: "::1"},
|
||||
{bindIP: "[::]", want: "::1"},
|
||||
{bindIP: "192.0.2.10", want: "192.0.2.10"},
|
||||
{bindIP: "2001:db8::10", want: "2001:db8::10"},
|
||||
{bindIP: "worker.internal", want: "worker.internal"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.bindIP, func(t *testing.T) {
|
||||
if got := miniAdminWorkerDialIP(tt.bindIP); got != tt.want {
|
||||
t.Fatalf("miniAdminWorkerDialIP(%q) = %q, want %q", tt.bindIP, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestMiniAdminAdvertisedIP verifies that the welcome message uses the
|
||||
// selected loopback address but replaces wildcard binds with a reachable host.
|
||||
func TestMiniAdminAdvertisedIP(t *testing.T) {
|
||||
oldMiniIP := miniIp
|
||||
oldAdminIP := miniAdminOptions.ip
|
||||
t.Cleanup(func() {
|
||||
miniIp = oldMiniIP
|
||||
miniAdminOptions.ip = oldAdminIP
|
||||
})
|
||||
|
||||
detectedIP := "192.0.2.10"
|
||||
miniIp = &detectedIP
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
adminIP string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "selected loopback",
|
||||
adminIP: "127.0.0.1",
|
||||
expected: "127.0.0.1",
|
||||
},
|
||||
{
|
||||
name: "IPv4 wildcard",
|
||||
adminIP: "0.0.0.0",
|
||||
expected: detectedIP,
|
||||
},
|
||||
{
|
||||
name: "IPv6 wildcard",
|
||||
adminIP: "::",
|
||||
expected: detectedIP,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
adminIP := tt.adminIP
|
||||
miniAdminOptions.ip = &adminIP
|
||||
if got := miniAdminAdvertisedIP(); got != tt.expected {
|
||||
t.Fatalf("miniAdminAdvertisedIP() = %q, want %q", got, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user