Go 1.27 TLS Defaults and OIDC Discovery Failure Modes
After SILO’s toolchain moved to Go 1.27, the Server TLS repair 48e184652 (“fix(tls): honor Go key exchange defaults across transports”, shipped in Server 20260916) removed explicit curve overrides across eight Server TLS configuration points. This article records the Go 1.27 TLS changes, the phase-based diagnostic method for OIDC discovery failures developed during the investigation of issue #154, and the health-endpoint behavior when an identity provider is unreachable at startup.
For the post-release retest analysis — why GODEBUG=tlsmlkem=0 disables hybrid key exchange but does not suppress the 12-byte ML-DSA signature algorithm offer, and the configuration options for TLS-inspecting middleboxes — see Pinned TLS Parameters and Handshake Compatibility.
What Go 1.27 Changed
- Explicit
CurvePreferencesnow override the ML-KEM compatibility switches.GODEBUG=tlsmlkem=0removes all ML-KEM hybrids from the default curve set;tlssecpmlkem=0removes only the P-256/P-384 hybrids introduced in Go 1.26 while retainingX25519MLKEM768. In Go 1.27, any application that setsCurvePreferencesexplicitly retains ML-KEM in that list regardless of theGODEBUGswitches. SILO Server had eight TLS configuration points setting an explicit list that includedX25519MLKEM768; commit48e184652removes all eight assignments and retires the helper so those configuration points use Go’s default curve set and honor theGODEBUGswitches. Stack-wide review confirmed thatsilo-pkg,mcli, and Console clients already used the standard library defaults, while Console’s HTTPS listener retains its separate P-256-only policy. ClientHellonow advertises ML-DSA signature algorithms (code points0x0904–0x0906). Because ML-DSA is a signature scheme rather than a key-exchange mechanism, disabling hybrid key exchange leaves the ML-DSA offer intact, and middleboxes that reject unknown signature-algorithm code points are unaffected by ML-KEM switches.ClientHellosize increased. Measured on identical source and dependencies: Go 1.26.5 defaults produce a 1497-byteClientHello; Go 1.27.1 defaults produce 1509 bytes; undertlsmlkem=0, the old explicit curve list produced a 275-byteClientHellowithout ML-KEM on Go 1.26.5, whereas Go 1.27.1 with that same explicit list still produced a 1509-byteClientHellocontaining ML-KEM. Upgrading the compiler alone altered the outbound handshake payload.- macOS root-CA resolution follows the main module’s
godirective. Whether a process honorsSSL_CERT_FILE/SSL_CERT_DIRor queries the macOS Keychain is controlled by thex509sslcertoverrideplatformGODEBUG setting, whose default tracks the top-level main module’sgodirective:go 1.26main modules ignore both variables on macOS in favor of the platform Keychain, whereasgo 1.27main modules honor them (regardless of oldergodirectives in library dependencies). On macOS, setting either environment variable replaces Keychain trust entirely with the specified file or directory; if that path points to a stale or incomplete bundle, certificate chains that the Keychain would have trusted fail verification until the variable is unset. - Unchanged TLS and platform baselines. TLS protocol versions, cipher suites, certificate and hostname verification, proxy resolution, and HTTP/2 negotiation remain unchanged, and audited Go 1.27 runtime changes such as the standard-library response-body drain cap (256 KiB / 50 ms) do not affect SILO. Go 1.27 binaries require macOS 13 or later, and the module graph across Server, Console, and
mcrequires Go ≥ 1.27.1.
Why the OIDC-Only Patch Was Withdrawn
An initial candidate patch cleared CurvePreferences only on the OIDC discovery transport. We discarded that approach because the same transport constructor backs identity plugins, notification and Lambda reachability probes, audit webhooks, and S3 cloud-storage tiering — patching only the two OIDC call sites would have left every other outbound TLS consumer on the hardcoded curve list. The merged fix removes the explicit curve overrides across all eight Server TLS configuration points while keeping certificate verification strict and avoiding protocol downgrades or silent fallbacks.
Diagnosing a Discovery Failure by Phase
The startup sequence runs: server start → identity subsystem initialization → fetch .well-known/openid-configuration (discovery) → fetch jwks_uri keys → IAM store ready → Console initialization. Console’s OIDC configuration dialog validates through the same server-side transport. A failure at any step leaves IAM offline; even if the discovery document loads, a 503 or timeout when fetching jwks_uri still prevents IAM from coming online.
Isolate the failure by the phase where the connection terminates:
- TCP reset immediately after
ClientHello(reset right aftertls_start): inspect ingress or middlebox handling of theClientHellopacket — proxyCONNECTpolicies, TLS terminators, or middleboxes filtering on packet size or extension code points. All Go 1.27 handshake changes surface at this step. - Reset or error after the TLS handshake completes (reset after
wrote_request): the TLS handshake succeeded; check HTTP-layer policies such as WAF rules,User-Agentallowlists (the server’s default User-Agent changed fromMinIOtoSilo), and layer-7 routing. Changing certificates or key-exchange settings will not affect failures at this stage. - x509 verification errors: inspect the certificate chain returned by the peer, the SNI server name sent, and the trust store selected by the process (on macOS, check whether
SSL_CERT_FILEorSSL_CERT_DIRis overriding the Keychain). - Run diagnostic probes from the exact network namespace and egress path of the failing process, matching source IP and proxy settings.
Health Endpoints and IAM Status
While IAM is offline, /minio/health/live and /minio/health/ready both continue to return HTTP 200, as neither probe gates on the identity subsystem. /minio/health/cluster checks identity initialization and returns 503 Service Unavailable with the response header X-Minio-Server-Status: iam-offline. Monitoring designed to catch broken IdP integrations should probe /minio/health/cluster alongside an authenticated request.
Identity initialization retries automatically in the background at randomized 0–3 s intervals; once the IdP becomes reachable, IAM comes online without a process restart (measured at sub-second to ~1.4 s in local tests). Background retries only recover from transient reachability failures — a ClientHello rejected by a middlebox will fail on every retry until the TLS configuration or middlebox policy is updated.
Discovery and JWKS Transport Parameters
The OIDC discovery and JWKS client constructs a dedicated http.Transport with the following parameters: HTTP/2 disabled (no h2 ALPN, HTTP/1.1 only); proxy settings read exclusively from HTTPS_PROXY and NO_PROXY (uppercase preferred; ALL_PROXY is ignored); DNS cache refresh defaulting to 30 s in Kubernetes/Docker environments and 10 min elsewhere (overridable via the DNS cache TTL setting); sequential address dialing without shuffling; and per-phase timeouts of 5 s per TCP dial, 10 s for the TLS handshake, and 1 min for response headers. No overall request timeout is set on the discovery or JWKS fetch itself, so a stalling IdP can hold startup initialization open indefinitely.
Attribution
This note distills the issue #154 investigation and the September Go 1.27 stack review. Each behavior described above was verified with synthetic experiments (ClientHello packet captures, fresh-process root-CA probes, and local fixture reproductions) and cross-checked against the Go 1.27 release notes and the crypto/tls and crypto/x509 standard library implementations.