Files
silo/cmd/tls_defaults_test.go
Feng Ruohang 48e1846525 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>
2026-09-09 18:50:12 +08:00

134 lines
4.4 KiB
Go

// 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()
}
}
})
}
}