Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,22 @@ Use [Download & Install](https://silo.pgsty.com/download/#client) to choose a cl
| Container image | [`pgsty/mc`](https://hub.docker.com/r/pgsty/mc), multi-arch for `linux/amd64` and `linux/arm64`, with `mc` as the entrypoint |
| Silo bundle | [`pgsty/silo`](https://hub.docker.com/r/pgsty/silo) includes the client as `mcli` with an `mc` compatibility alias |

### Go and TLS compatibility

Go 1.27 builds require macOS 13 or later. On macOS, builds targeting Go 1.27
replace Keychain trust with on-disk roots and Go's verifier when either
`SSL_CERT_FILE` or `SSL_CERT_DIR` is set. Stale or incomplete CA paths can break
previously trusted connections; unset inherited values to restore Keychain
trust. Certificates in mcli's configured `CAs` directory are still added to the
selected root pool.

S3, Admin, and alias TLS connections use Go's default key exchanges.
`GODEBUG=tlsmlkem=0` can temporarily accommodate an ML-KEM-intolerant endpoint;
it does not disable certificate verification or ML-DSA signatures.
`GODEBUG=tlssecpmlkem=0` is the narrower option for disabling only the SecP
hybrids while retaining X25519MLKEM768. The relevant
changes are described in the [Go release notes](https://go.dev/doc/go1.27).

## Quick Start

Standalone archives and Linux packages expose the command as `mcli`:
Expand Down
71 changes: 71 additions & 0 deletions cmd/tls_defaults_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Copyright (c) 2026 Pigsty
// SPDX-License-Identifier: AGPL-3.0-or-later

package cmd

import (
"crypto/tls"
"crypto/x509"
"io"
"net/http"
"net/http/httptest"
"slices"
"testing"
"time"
)

func TestClientTLSKeyExchangeDefaults(t *testing.T) {
for _, debug := range []string{"tlsmlkem=0", "tlsmlkem=1"} {
t.Run(debug, func(t *testing.T) {
t.Setenv("GODEBUG", debug)
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{GetConfigForClient: func(hello *tls.ClientHelloInfo) (*tls.Config, error) {
select {
case hellos <- slices.Clone(hello.SupportedCurves):
default:
}
return nil, nil
}}
server.StartTLS()
defer server.Close()
roots := x509.NewCertPool()
roots.AddCert(server.Certificate())
previousRoots := globalRootCAs
globalRootCAs = roots
t.Cleanup(func() { globalRootCAs = previousRoots })
config := &Config{HostURL: server.URL}
config.initTransport(false)
aliasTransport := &http.Transport{DialTLSContext: newCustomDialTLSContext(&tls.Config{RootCAs: roots})}
defer aliasTransport.CloseIdleConnections()
for name, transport := range map[string]http.RoundTripper{
"S3-and-admin": config.Transport,
"alias-dialer": aliasTransport,
} {
t.Run(name, func(t *testing.T) {
client := &http.Client{Transport: transport, Timeout: 5 * time.Second}
req, err := http.NewRequest(http.MethodGet, server.URL, nil)
if err != nil {
t.Fatal(err)
}
// The S3 transport is wrapped; close this connection explicitly.
req.Close = true
resp, err := client.Do(req)
if err != nil {
t.Fatal(err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK || len(resp.TLS.VerifiedChains) == 0 {
t.Fatalf("status %d, verified chains %d", resp.StatusCode, len(resp.TLS.VerifiedChains))
}
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)
}
})
}
})
}
}
Loading