From c463b49f468f7e1232308ecc931e217e61f6d779 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Mon, 1 Jun 2026 07:08:47 -0400 Subject: [PATCH] feat(client-go): accept gateway cert by default over TLS --- clients/go/mxgateway/client.go | 18 +++++++- clients/go/mxgateway/client_tls_test.go | 59 +++++++++++++++++++++++++ clients/go/mxgateway/options.go | 4 ++ 3 files changed, 79 insertions(+), 2 deletions(-) create mode 100644 clients/go/mxgateway/client_tls_test.go diff --git a/clients/go/mxgateway/client.go b/clients/go/mxgateway/client.go index 2aac029..9e3fab7 100644 --- a/clients/go/mxgateway/client.go +++ b/clients/go/mxgateway/client.go @@ -222,10 +222,24 @@ func resolveTransportCredentials(opts Options) (credentials.TransportCredentials return credentials.NewTLS(cfg), nil } - return credentials.NewTLS(&tls.Config{ + return credentials.NewTLS(tlsConfigForOptions(opts)), nil +} + +// tlsConfigForOptions returns the *tls.Config for the no-CA, no-custom-config TLS path. +// It returns nil when the caller should use a different credentials path (CA file or custom TLSConfig). +// Exposed as an internal helper so unit tests can assert the InsecureSkipVerify posture. +func tlsConfigForOptions(opts Options) *tls.Config { + // CA file and custom TLSConfig take their own paths in resolveTransportCredentials. + if opts.CACertFile != "" || opts.TLSConfig != nil { + return nil + } + return &tls.Config{ MinVersion: tls.VersionTLS12, ServerName: opts.ServerNameOverride, - }), nil + //nolint:gosec // internal tool; self-signed cert is the expected gateway default; + // opt-in to strict verification via RequireCertificateValidation. + InsecureSkipVerify: !opts.RequireCertificateValidation, + } } // OpenSessionOptions describes fields used to create an OpenSessionRequest. diff --git a/clients/go/mxgateway/client_tls_test.go b/clients/go/mxgateway/client_tls_test.go new file mode 100644 index 0000000..7a88ccc --- /dev/null +++ b/clients/go/mxgateway/client_tls_test.go @@ -0,0 +1,59 @@ +package mxgateway + +import ( + "crypto/tls" + "testing" +) + +// tlsConfigFromOptions is the internal helper under test. +// It extracts the *tls.Config from the no-CA TLS path of resolveTransportCredentials. +// We exercise it directly to avoid needing a real dial target. + +func TestTLSInsecureSkipVerify_DefaultTrue(t *testing.T) { + cfg := tlsConfigForOptions(Options{ + Endpoint: "localhost:5120", + }) + if cfg == nil { + t.Fatal("expected non-nil tls.Config") + } + if !cfg.InsecureSkipVerify { + t.Error("InsecureSkipVerify should be true by default when no CA is pinned") + } +} + +func TestTLSInsecureSkipVerify_FalseWhenRequireCertificateValidation(t *testing.T) { + cfg := tlsConfigForOptions(Options{ + Endpoint: "localhost:5120", + RequireCertificateValidation: true, + }) + if cfg == nil { + t.Fatal("expected non-nil tls.Config") + } + if cfg.InsecureSkipVerify { + t.Error("InsecureSkipVerify should be false when RequireCertificateValidation is true") + } +} + +func TestTLSInsecureSkipVerify_FalseWhenCACertFileSet(t *testing.T) { + // When a CA file is pinned, the CA-verification path is taken instead. + // tlsConfigForOptions should return nil (the CA path does not use our helper). + cfg := tlsConfigForOptions(Options{ + Endpoint: "localhost:5120", + CACertFile: "/some/ca.pem", + }) + if cfg != nil { + t.Error("expected nil tls.Config when CACertFile is set (CA path taken)") + } +} + +func TestTLSInsecureSkipVerify_FalseWhenCustomTLSConfig(t *testing.T) { + // When TLSConfig is supplied explicitly, our default skip-verify must not overwrite it. + custom := &tls.Config{MinVersion: tls.VersionTLS13} + cfg := tlsConfigForOptions(Options{ + Endpoint: "localhost:5120", + TLSConfig: custom, + }) + if cfg != nil { + t.Error("expected nil tls.Config when TLSConfig is already set (custom config path taken)") + } +} diff --git a/clients/go/mxgateway/options.go b/clients/go/mxgateway/options.go index 12b0e34..732c662 100644 --- a/clients/go/mxgateway/options.go +++ b/clients/go/mxgateway/options.go @@ -34,6 +34,10 @@ type Options struct { TransportCredentials credentials.TransportCredentials // DialOptions are appended to the gRPC dial options after the defaults. DialOptions []grpc.DialOption + // RequireCertificateValidation forces TLS certificate verification even when + // no CACertFile is pinned. Default false: the gateway's self-signed cert is + // accepted without verification (internal-tool posture). + RequireCertificateValidation bool } // BrowseChildrenOptions configures lazy Galaxy hierarchy walks performed by