mirror of
https://github.com/pgsty/minio.git
synced 2026-09-19 17:08:28 +03:00
fix(tls): honor Go key exchange defaults across transports
Remove the eight explicit curve overrides so Go 1.27 honors tlsmlkem=0 across Server listeners, node links and outbound transports. Remove the unused shared curve option and add wire-level regression coverage. Document CA trust and TLS upgrade behavior, retain the investigation artifacts, and exclude their synthetic routes from the rebrand guard. The product compatibility baseline remains unchanged. Validation: focused race tests, HTTP tests, lint, compatibility guard positive/negative controls, and a fresh Linux build with three isolated OIDC integration scenarios all pass. Adversarial review: Claude Code Fable 5.1, max effort. Final verdict: APPROVE FOR COMMIT. Signed-off-by: Feng Ruohang <rh@vonng.com>
This commit is contained in:
+4
-6
@@ -51,9 +51,8 @@ func initGlobalGrid(ctx context.Context, eps EndpointServerPools) error {
|
||||
grid.ContextDialer(xhttp.DialContextWithLookupHost(lookupHost, xhttp.NewInternodeDialContext(rest.DefaultTimeout, globalTCPOptions.ForWebsocket()))),
|
||||
newCachedAuthToken(),
|
||||
&tls.Config{
|
||||
RootCAs: globalRootCAs,
|
||||
CipherSuites: crypto.TLSCiphers(),
|
||||
CurvePreferences: crypto.TLSCurveIDs(),
|
||||
RootCAs: globalRootCAs,
|
||||
CipherSuites: crypto.TLSCiphers(),
|
||||
}),
|
||||
Local: local,
|
||||
Hosts: hosts,
|
||||
@@ -84,9 +83,8 @@ func initGlobalLockGrid(ctx context.Context, eps EndpointServerPools) error {
|
||||
grid.ContextDialer(xhttp.DialContextWithLookupHost(lookupHost, xhttp.NewInternodeDialContext(rest.DefaultTimeout, globalTCPOptions.ForWebsocket()))),
|
||||
newCachedAuthToken(),
|
||||
&tls.Config{
|
||||
RootCAs: globalRootCAs,
|
||||
CipherSuites: crypto.TLSCiphers(),
|
||||
CurvePreferences: crypto.TLSCurveIDs(),
|
||||
RootCAs: globalRootCAs,
|
||||
CipherSuites: crypto.TLSCiphers(),
|
||||
}, grid.RouteLockPath),
|
||||
Local: local,
|
||||
Hosts: hosts,
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
// Copyright (c) 2026 Pigsty
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/minio/minio/internal/config/etcd"
|
||||
)
|
||||
|
||||
func TestOutboundTLSKeyExchangeDefaults(t *testing.T) {
|
||||
for _, debug := range []string{"tlsmlkem=0", "tlsmlkem=1"} {
|
||||
t.Run(debug, func(t *testing.T) {
|
||||
t.Setenv("GODEBUG", debug)
|
||||
for _, version := range []uint16{tls.VersionTLS12, tls.VersionTLS13} {
|
||||
t.Run(tls.VersionName(version), func(t *testing.T) {
|
||||
hellos := make(chan []tls.CurveID, 1)
|
||||
server := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
_, _ = io.WriteString(w, "ok")
|
||||
}))
|
||||
server.TLS = &tls.Config{
|
||||
MinVersion: version, MaxVersion: version,
|
||||
GetConfigForClient: func(hello *tls.ClientHelloInfo) (*tls.Config, error) {
|
||||
select {
|
||||
case hellos <- slices.Clone(hello.SupportedCurves):
|
||||
default:
|
||||
}
|
||||
return nil, nil
|
||||
},
|
||||
}
|
||||
server.StartTLS()
|
||||
t.Cleanup(server.Close)
|
||||
roots := x509.NewCertPool()
|
||||
roots.AddCert(server.Certificate())
|
||||
|
||||
dir := t.TempDir()
|
||||
certFile, keyFile := filepath.Join(dir, "public.crt"), filepath.Join(dir, "private.key")
|
||||
cert := server.TLS.Certificates[0]
|
||||
key, err := x509.MarshalPKCS8PrivateKey(cert.PrivateKey)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for path, block := range map[string]*pem.Block{
|
||||
certFile: {Type: "CERTIFICATE", Bytes: cert.Certificate[0]},
|
||||
keyFile: {Type: "PRIVATE KEY", Bytes: key},
|
||||
} {
|
||||
if err := os.WriteFile(path, pem.EncodeToMemory(block), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
t.Setenv(etcd.EnvEtcdEndpoints, server.URL)
|
||||
t.Setenv(etcd.EnvEtcdClientCert, "")
|
||||
t.Setenv(etcd.EnvEtcdClientCertKey, "")
|
||||
etcdConfig, err := etcd.LookupConfig(etcd.DefaultKVS, roots)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for name, transport := range map[string]*http.Transport{
|
||||
"external": NewHTTPTransport(),
|
||||
"internode": NewInternodeHTTPTransport(1)().(*http.Transport),
|
||||
"replication": NewRemoteTargetHTTPTransport(false)(),
|
||||
"cloud-client-cert": NewHTTPTransportWithClientCerts(certFile, keyFile).(*http.Transport),
|
||||
"etcd": {TLSClientConfig: etcdConfig.TLS},
|
||||
} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
defer transport.CloseIdleConnections()
|
||||
transport.Proxy = nil
|
||||
transport.TLSClientConfig.RootCAs = roots
|
||||
client := &http.Client{Transport: transport, Timeout: 5 * time.Second}
|
||||
resp, err := client.Get(server.URL)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK || resp.TLS.Version != version {
|
||||
t.Fatalf("status %d, TLS %s", resp.StatusCode, tls.VersionName(resp.TLS.Version))
|
||||
}
|
||||
curves := <-hellos
|
||||
if got, want := slices.Contains(curves, tls.X25519MLKEM768), debug == "tlsmlkem=1"; got != want {
|
||||
t.Errorf("ML-KEM offered = %v, want %v; curves %v", got, want, curves)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerTLSKeyExchangeDefaults(t *testing.T) {
|
||||
for _, debug := range []string{"tlsmlkem=0", "tlsmlkem=1"} {
|
||||
t.Run(debug, func(t *testing.T) {
|
||||
t.Setenv("GODEBUG", debug)
|
||||
// Reuse httptest's certificate with the actual Server TLS constructor.
|
||||
seed := httptest.NewTLSServer(http.NotFoundHandler())
|
||||
cert := seed.TLS.Certificates[0]
|
||||
seed.Close()
|
||||
server := httptest.NewUnstartedServer(http.NotFoundHandler())
|
||||
server.TLS = newTLSConfig(func(*tls.ClientHelloInfo) (*tls.Certificate, error) { return &cert, nil })
|
||||
server.StartTLS()
|
||||
defer server.Close()
|
||||
roots := x509.NewCertPool()
|
||||
leaf, err := x509.ParseCertificate(cert.Certificate[0])
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
roots.AddCert(leaf)
|
||||
for _, curve := range []tls.CurveID{tls.X25519MLKEM768, tls.CurveP256} {
|
||||
conn, err := tls.Dial("tcp", server.Listener.Addr().String(), &tls.Config{
|
||||
RootCAs: roots, MinVersion: tls.VersionTLS13, CurvePreferences: []tls.CurveID{curve},
|
||||
})
|
||||
wantSuccess := curve == tls.CurveP256 || debug == "tlsmlkem=1"
|
||||
if (err == nil) != wantSuccess {
|
||||
t.Errorf("curve %v: error %v, want success %v", curve, err, wantSuccess)
|
||||
}
|
||||
if conn != nil {
|
||||
_ = conn.Close()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
+23
-28
@@ -608,13 +608,12 @@ func GetDefaultConnSettings() xhttp.ConnSettings {
|
||||
// connections.
|
||||
func NewInternodeHTTPTransport(maxIdleConnsPerHost int) func() http.RoundTripper {
|
||||
return xhttp.ConnSettings{
|
||||
LookupHost: globalDNSCache.LookupHost,
|
||||
DialTimeout: rest.DefaultTimeout,
|
||||
RootCAs: globalRootCAs,
|
||||
CipherSuites: crypto.TLSCiphers(),
|
||||
CurvePreferences: crypto.TLSCurveIDs(),
|
||||
EnableHTTP2: false,
|
||||
TCPOptions: globalTCPOptions,
|
||||
LookupHost: globalDNSCache.LookupHost,
|
||||
DialTimeout: rest.DefaultTimeout,
|
||||
RootCAs: globalRootCAs,
|
||||
CipherSuites: crypto.TLSCiphers(),
|
||||
EnableHTTP2: false,
|
||||
TCPOptions: globalTCPOptions,
|
||||
}.NewInternodeHTTPTransport(maxIdleConnsPerHost)
|
||||
}
|
||||
|
||||
@@ -622,13 +621,12 @@ func NewInternodeHTTPTransport(maxIdleConnsPerHost int) func() http.RoundTripper
|
||||
// used while communicating with the cloud backends.
|
||||
func NewHTTPTransportWithClientCerts(clientCert, clientKey string) http.RoundTripper {
|
||||
s := xhttp.ConnSettings{
|
||||
LookupHost: globalDNSCache.LookupHost,
|
||||
DialTimeout: defaultDialTimeout,
|
||||
RootCAs: globalRootCAs,
|
||||
CipherSuites: crypto.TLSCiphersBackwardCompatible(),
|
||||
CurvePreferences: crypto.TLSCurveIDs(),
|
||||
TCPOptions: globalTCPOptions,
|
||||
EnableHTTP2: false,
|
||||
LookupHost: globalDNSCache.LookupHost,
|
||||
DialTimeout: defaultDialTimeout,
|
||||
RootCAs: globalRootCAs,
|
||||
CipherSuites: crypto.TLSCiphersBackwardCompatible(),
|
||||
TCPOptions: globalTCPOptions,
|
||||
EnableHTTP2: false,
|
||||
}
|
||||
|
||||
if clientCert != "" && clientKey != "" {
|
||||
@@ -660,13 +658,12 @@ const defaultDialTimeout = 5 * time.Second
|
||||
// NewHTTPTransportWithTimeout allows setting a timeout.
|
||||
func NewHTTPTransportWithTimeout(timeout time.Duration) *http.Transport {
|
||||
return xhttp.ConnSettings{
|
||||
LookupHost: globalDNSCache.LookupHost,
|
||||
DialTimeout: defaultDialTimeout,
|
||||
RootCAs: globalRootCAs,
|
||||
TCPOptions: globalTCPOptions,
|
||||
CipherSuites: crypto.TLSCiphersBackwardCompatible(),
|
||||
CurvePreferences: crypto.TLSCurveIDs(),
|
||||
EnableHTTP2: false,
|
||||
LookupHost: globalDNSCache.LookupHost,
|
||||
DialTimeout: defaultDialTimeout,
|
||||
RootCAs: globalRootCAs,
|
||||
TCPOptions: globalTCPOptions,
|
||||
CipherSuites: crypto.TLSCiphersBackwardCompatible(),
|
||||
EnableHTTP2: false,
|
||||
}.NewHTTPTransportWithTimeout(timeout)
|
||||
}
|
||||
|
||||
@@ -674,12 +671,11 @@ func NewHTTPTransportWithTimeout(timeout time.Duration) *http.Transport {
|
||||
// used while communicating with the remote replication targets.
|
||||
func NewRemoteTargetHTTPTransport(insecure bool) func() *http.Transport {
|
||||
return xhttp.ConnSettings{
|
||||
LookupHost: globalDNSCache.LookupHost,
|
||||
RootCAs: globalRootCAs,
|
||||
CipherSuites: crypto.TLSCiphersBackwardCompatible(),
|
||||
CurvePreferences: crypto.TLSCurveIDs(),
|
||||
TCPOptions: globalTCPOptions,
|
||||
EnableHTTP2: false,
|
||||
LookupHost: globalDNSCache.LookupHost,
|
||||
RootCAs: globalRootCAs,
|
||||
CipherSuites: crypto.TLSCiphersBackwardCompatible(),
|
||||
TCPOptions: globalTCPOptions,
|
||||
EnableHTTP2: false,
|
||||
}.NewRemoteTargetHTTPTransport(insecure)
|
||||
}
|
||||
|
||||
@@ -986,7 +982,6 @@ func newTLSConfig(getCert certs.GetCertificateFunc) *tls.Config {
|
||||
} else {
|
||||
tlsConfig.CipherSuites = crypto.TLSCiphersBackwardCompatible()
|
||||
}
|
||||
tlsConfig.CurvePreferences = crypto.TLSCurveIDs()
|
||||
return tlsConfig
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user