fix(nodes): honor TLS verify mode skip/pin for remote node operations (#5264)

The node probe honored the per-node TlsVerifyMode (skip/pin) but
runtime.Remote used a shared client with no TLSClientConfig, so traffic
sync and every other remote op fell back to system-CA verification and
failed against self-signed nodes even after the operator set skip/pin.

Move the TLS client builder into the runtime layer (HTTPClientForNode /
DecodeCertPin) as the single source of truth, have Remote build and cache
its per-node client through it, and delegate the service probe to the same
builder so the two paths can no longer diverge.
This commit is contained in:
MHSanaei
2026-06-13 11:11:02 +02:00
parent 9a8247fa78
commit 4c8d3cb625
4 changed files with 271 additions and 83 deletions
+20 -10
View File
@@ -23,15 +23,6 @@ import (
const remoteHTTPTimeout = 10 * time.Second
var remoteHTTPClient = &http.Client{
Transport: &http.Transport{
MaxIdleConns: 64,
MaxIdleConnsPerHost: 4,
IdleConnTimeout: 60 * time.Second,
DialContext: netsafe.SSRFGuardedDialContext,
},
}
type envelope struct {
Success bool `json:"success"`
Msg string `json:"msg"`
@@ -43,6 +34,12 @@ type Remote struct {
mu sync.RWMutex
remoteIDByTag map[string]int
// Per-node client honoring the TLS verify mode, built once and reused; a
// node config change drops the cached Remote so the next one rebuilds it.
clientOnce sync.Once
client *http.Client
clientErr error
}
type RemoteInboundOption struct {
@@ -61,6 +58,15 @@ func NewRemote(n *model.Node) *Remote {
func (r *Remote) Name() string { return "node:" + r.node.Name }
// httpClient lazily builds and caches the per-node client honoring the TLS
// verify mode, so Remote ops don't fall back to system CA on skip/pin (#5264).
func (r *Remote) httpClient() (*http.Client, error) {
r.clientOnce.Do(func() {
r.client, r.clientErr = HTTPClientForNode(r.node)
})
return r.client, r.clientErr
}
func (r *Remote) baseURL() (string, error) {
addr, err := netsafe.NormalizeHost(r.node.Address)
if err != nil {
@@ -129,7 +135,11 @@ func (r *Remote) do(ctx context.Context, method, path string, body any) (*envelo
req.Header.Set("Content-Type", contentType)
}
resp, err := remoteHTTPClient.Do(req)
client, err := r.httpClient()
if err != nil {
return nil, err
}
resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("%s %s: %w", method, path, err)
}