mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-10-07 14:57:48 +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
@@ -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