committed by
GitHub
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
31 changed files with 2021 additions and 191 deletions
-
1docker/compose/master-cloud.toml
-
360go.sum
-
164test/erasure_coding/admin_dockertest/ec_integration_test.go
-
3weed/admin/dash/admin_lock_manager.go
-
61weed/admin/dash/admin_presence_lock.go
-
9weed/admin/dash/admin_server.go
-
50weed/admin/dash/plugin_api.go
-
2weed/admin/handlers/admin_handlers.go
-
56weed/admin/plugin/config_store.go
-
57weed/admin/plugin/plugin.go
-
73weed/admin/plugin/plugin_monitor.go
-
613weed/admin/plugin/plugin_scheduler.go
-
23weed/admin/plugin/plugin_scheduler_test.go
-
31weed/admin/plugin/scheduler_config.go
-
112weed/admin/plugin/scheduler_status.go
-
4weed/admin/plugin/types.go
-
211weed/admin/view/app/plugin.templ
-
2weed/admin/view/app/plugin_templ.go
-
6weed/cluster/admin_locks.go
-
1weed/command/scaffold/master.toml
-
2weed/pb/plugin.proto
-
24weed/pb/plugin_pb/plugin.pb.go
-
17weed/pb/server_address.go
-
58weed/pb/server_address_test.go
-
1weed/plugin/worker/admin_script_handler.go
-
1weed/plugin/worker/erasure_coding_handler.go
-
1weed/plugin/worker/vacuum_handler.go
-
1weed/plugin/worker/volume_balance_handler.go
-
37weed/security/tls.go
-
219weed/security/tls_sni_test.go
-
12weed/server/master_server.go
360
go.sum
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -0,0 +1,61 @@ |
|||
package dash |
|||
|
|||
import ( |
|||
"time" |
|||
|
|||
"github.com/seaweedfs/seaweedfs/weed/cluster" |
|||
"github.com/seaweedfs/seaweedfs/weed/wdclient" |
|||
"github.com/seaweedfs/seaweedfs/weed/wdclient/exclusive_locks" |
|||
) |
|||
|
|||
const adminPresenceClientName = "admin-server" |
|||
|
|||
type adminPresenceLock struct { |
|||
locker *exclusive_locks.ExclusiveLocker |
|||
stopCh chan struct{} |
|||
} |
|||
|
|||
func newAdminPresenceLock(masterClient *wdclient.MasterClient) *adminPresenceLock { |
|||
if masterClient == nil { |
|||
return nil |
|||
} |
|||
return &adminPresenceLock{ |
|||
locker: exclusive_locks.NewExclusiveLocker(masterClient, cluster.AdminServerPresenceLockName), |
|||
stopCh: make(chan struct{}), |
|||
} |
|||
} |
|||
|
|||
func (l *adminPresenceLock) Start() { |
|||
if l == nil || l.locker == nil { |
|||
return |
|||
} |
|||
l.locker.SetMessage("admin server connected") |
|||
go func() { |
|||
ticker := time.NewTicker(5 * time.Second) |
|||
defer ticker.Stop() |
|||
for { |
|||
if !l.locker.IsLocked() { |
|||
l.locker.RequestLock(adminPresenceClientName) |
|||
} |
|||
select { |
|||
case <-l.stopCh: |
|||
return |
|||
case <-ticker.C: |
|||
} |
|||
} |
|||
}() |
|||
} |
|||
|
|||
func (l *adminPresenceLock) Stop() { |
|||
if l == nil { |
|||
return |
|||
} |
|||
select { |
|||
case <-l.stopCh: |
|||
default: |
|||
close(l.stopCh) |
|||
} |
|||
if l.locker != nil { |
|||
l.locker.ReleaseLock() |
|||
} |
|||
} |
|||
@ -0,0 +1,31 @@ |
|||
package plugin |
|||
|
|||
import "time" |
|||
|
|||
const ( |
|||
defaultSchedulerIdleSleep = 613 * time.Second |
|||
) |
|||
|
|||
type SchedulerConfig struct { |
|||
IdleSleepSeconds int32 `json:"idle_sleep_seconds"` |
|||
} |
|||
|
|||
func DefaultSchedulerConfig() SchedulerConfig { |
|||
return SchedulerConfig{ |
|||
IdleSleepSeconds: int32(defaultSchedulerIdleSleep / time.Second), |
|||
} |
|||
} |
|||
|
|||
func normalizeSchedulerConfig(cfg SchedulerConfig) SchedulerConfig { |
|||
if cfg.IdleSleepSeconds <= 0 { |
|||
return DefaultSchedulerConfig() |
|||
} |
|||
return cfg |
|||
} |
|||
|
|||
func (c SchedulerConfig) IdleSleepDuration() time.Duration { |
|||
if c.IdleSleepSeconds <= 0 { |
|||
return defaultSchedulerIdleSleep |
|||
} |
|||
return time.Duration(c.IdleSleepSeconds) * time.Second |
|||
} |
|||
2
weed/admin/view/app/plugin_templ.go
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -0,0 +1,6 @@ |
|||
package cluster |
|||
|
|||
const ( |
|||
AdminShellLockName = "shell" |
|||
AdminServerPresenceLockName = "admin-server" |
|||
) |
|||
@ -0,0 +1,219 @@ |
|||
package security |
|||
|
|||
import ( |
|||
"context" |
|||
"crypto/ecdsa" |
|||
"crypto/elliptic" |
|||
"crypto/rand" |
|||
"crypto/tls" |
|||
"crypto/x509" |
|||
"crypto/x509/pkix" |
|||
"math/big" |
|||
"net" |
|||
"testing" |
|||
"time" |
|||
|
|||
"google.golang.org/grpc/credentials" |
|||
"google.golang.org/grpc/security/advancedtls" |
|||
) |
|||
|
|||
func generateSelfSignedCert(t *testing.T) (tls.Certificate, *x509.CertPool) { |
|||
t.Helper() |
|||
|
|||
caKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) |
|||
if err != nil { |
|||
t.Fatalf("generate CA key: %v", err) |
|||
} |
|||
caTemplate := &x509.Certificate{ |
|||
SerialNumber: big.NewInt(1), |
|||
Subject: pkix.Name{CommonName: "Test CA"}, |
|||
NotBefore: time.Now().Add(-time.Hour), |
|||
NotAfter: time.Now().Add(time.Hour), |
|||
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign, |
|||
BasicConstraintsValid: true, |
|||
IsCA: true, |
|||
} |
|||
caDER, err := x509.CreateCertificate(rand.Reader, caTemplate, caTemplate, &caKey.PublicKey, caKey) |
|||
if err != nil { |
|||
t.Fatalf("create CA cert: %v", err) |
|||
} |
|||
caCert, err := x509.ParseCertificate(caDER) |
|||
if err != nil { |
|||
t.Fatalf("parse CA cert: %v", err) |
|||
} |
|||
|
|||
leafKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) |
|||
if err != nil { |
|||
t.Fatalf("generate leaf key: %v", err) |
|||
} |
|||
leafTemplate := &x509.Certificate{ |
|||
SerialNumber: big.NewInt(2), |
|||
Subject: pkix.Name{CommonName: "localhost"}, |
|||
NotBefore: time.Now().Add(-time.Hour), |
|||
NotAfter: time.Now().Add(time.Hour), |
|||
KeyUsage: x509.KeyUsageDigitalSignature, |
|||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, |
|||
DNSNames: []string{"localhost"}, |
|||
IPAddresses: []net.IP{net.IPv4(127, 0, 0, 1), net.IPv6loopback}, |
|||
} |
|||
leafDER, err := x509.CreateCertificate(rand.Reader, leafTemplate, caCert, &leafKey.PublicKey, caKey) |
|||
if err != nil { |
|||
t.Fatalf("create leaf cert: %v", err) |
|||
} |
|||
|
|||
leafCert := tls.Certificate{ |
|||
Certificate: [][]byte{leafDER}, |
|||
PrivateKey: leafKey, |
|||
} |
|||
|
|||
pool := x509.NewCertPool() |
|||
pool.AddCert(caCert) |
|||
|
|||
return leafCert, pool |
|||
} |
|||
|
|||
func startTLSListenerCapturingSNI(t *testing.T, cert tls.Certificate) (string, <-chan string) { |
|||
t.Helper() |
|||
|
|||
sniChan := make(chan string, 1) |
|||
tlsConfig := &tls.Config{ |
|||
Certificates: []tls.Certificate{cert}, |
|||
GetConfigForClient: func(hello *tls.ClientHelloInfo) (*tls.Config, error) { |
|||
select { |
|||
case sniChan <- hello.ServerName: |
|||
close(sniChan) |
|||
default: |
|||
} |
|||
return nil, nil |
|||
}, |
|||
} |
|||
|
|||
ln, err := tls.Listen("tcp", "127.0.0.1:0", tlsConfig) |
|||
if err != nil { |
|||
t.Fatalf("tls.Listen: %v", err) |
|||
} |
|||
t.Cleanup(func() { ln.Close() }) |
|||
|
|||
go func() { |
|||
conn, err := ln.Accept() |
|||
if err != nil { |
|||
return |
|||
} |
|||
defer conn.Close() |
|||
buf := make([]byte, 1) |
|||
conn.Read(buf) |
|||
}() |
|||
|
|||
return ln.Addr().String(), sniChan |
|||
} |
|||
|
|||
func newSNIStrippingCreds(t *testing.T, cert tls.Certificate, pool *x509.CertPool) credentials.TransportCredentials { |
|||
t.Helper() |
|||
clientCreds, err := advancedtls.NewClientCreds(&advancedtls.Options{ |
|||
IdentityOptions: advancedtls.IdentityCertificateOptions{ |
|||
Certificates: []tls.Certificate{cert}, |
|||
}, |
|||
RootOptions: advancedtls.RootCertificateOptions{ |
|||
RootCertificates: pool, |
|||
}, |
|||
VerificationType: advancedtls.CertVerification, |
|||
AdditionalPeerVerification: func(params *advancedtls.HandshakeVerificationInfo) (*advancedtls.PostHandshakeVerificationResults, error) { |
|||
return &advancedtls.PostHandshakeVerificationResults{}, nil |
|||
}, |
|||
}) |
|||
if err != nil { |
|||
t.Fatalf("NewClientCreds: %v", err) |
|||
} |
|||
return &SNIStrippingTransportCredentials{creds: clientCreds} |
|||
} |
|||
|
|||
func TestSNI_HostnameStripsPort(t *testing.T) { |
|||
cert, pool := generateSelfSignedCert(t) |
|||
wrapped := newSNIStrippingCreds(t, cert, pool) |
|||
addr, sniChan := startTLSListenerCapturingSNI(t, cert) |
|||
|
|||
conn, err := net.DialTimeout("tcp", addr, 2*time.Second) |
|||
if err != nil { |
|||
t.Fatalf("dial: %v", err) |
|||
} |
|||
defer conn.Close() |
|||
|
|||
// gRPC passes "host:port" as authority; SNI wrapper strips the port
|
|||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) |
|||
defer cancel() |
|||
_, _, err = wrapped.ClientHandshake(ctx, "localhost:"+portFromAddr(addr), conn) |
|||
if err != nil { |
|||
t.Fatalf("ClientHandshake: %v", err) |
|||
} |
|||
|
|||
select { |
|||
case sni := <-sniChan: |
|||
if sni != "localhost" { |
|||
t.Errorf("SNI = %q, want %q", sni, "localhost") |
|||
} |
|||
case <-time.After(2 * time.Second): |
|||
t.Fatal("timed out waiting for SNI") |
|||
} |
|||
} |
|||
|
|||
func TestSNI_IPAddressEmptySNI(t *testing.T) { |
|||
cert, pool := generateSelfSignedCert(t) |
|||
wrapped := newSNIStrippingCreds(t, cert, pool) |
|||
addr, sniChan := startTLSListenerCapturingSNI(t, cert) |
|||
|
|||
conn, err := net.DialTimeout("tcp", addr, 2*time.Second) |
|||
if err != nil { |
|||
t.Fatalf("dial: %v", err) |
|||
} |
|||
defer conn.Close() |
|||
|
|||
// RFC 6066: IP addresses MUST NOT be sent as SNI; Go's TLS sends empty ServerName for IPs
|
|||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) |
|||
defer cancel() |
|||
_, _, err = wrapped.ClientHandshake(ctx, "127.0.0.1:"+portFromAddr(addr), conn) |
|||
if err != nil { |
|||
t.Fatalf("ClientHandshake: %v", err) |
|||
} |
|||
|
|||
select { |
|||
case sni := <-sniChan: |
|||
if sni != "" { |
|||
t.Errorf("SNI = %q, want empty string", sni) |
|||
} |
|||
case <-time.After(2 * time.Second): |
|||
t.Fatal("timed out waiting for SNI") |
|||
} |
|||
} |
|||
|
|||
func TestSNI_IPv6AddressEmptySNI(t *testing.T) { |
|||
cert, pool := generateSelfSignedCert(t) |
|||
wrapped := newSNIStrippingCreds(t, cert, pool) |
|||
addr, sniChan := startTLSListenerCapturingSNI(t, cert) |
|||
|
|||
conn, err := net.DialTimeout("tcp", addr, 2*time.Second) |
|||
if err != nil { |
|||
t.Fatalf("dial: %v", err) |
|||
} |
|||
defer conn.Close() |
|||
|
|||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) |
|||
defer cancel() |
|||
_, _, err = wrapped.ClientHandshake(ctx, "[::1]:"+portFromAddr(addr), conn) |
|||
if err != nil { |
|||
t.Fatalf("ClientHandshake: %v", err) |
|||
} |
|||
|
|||
select { |
|||
case sni := <-sniChan: |
|||
if sni != "" { |
|||
t.Errorf("SNI = %q, want empty string", sni) |
|||
} |
|||
case <-time.After(2 * time.Second): |
|||
t.Fatal("timed out waiting for SNI") |
|||
} |
|||
} |
|||
|
|||
func portFromAddr(addr string) string { |
|||
_, port, _ := net.SplitHostPort(addr) |
|||
return port |
|||
} |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue