feat(ledgerctl): add --tls-server-name to verify by name while dialing by IP#1595
Conversation
…g by IP TLS verification matches the --server host against the certificate SANs. Inside a TLS cluster pod, the operator-issued cert covers only the in-cluster DNS names (never localhost/127.0.0.1), so the default localhost:8888 dial fails with a name mismatch and ledgerctl is unusable. Add a persistent --tls-server-name flag (env LEDGERCTL_TLS_SERVER_NAME, profile tlsServerName) that overrides the verification hostname independently of the dial address, letting a client dial by IP/loopback while still validating the cert against a covered SAN. Rejected together with --insecure, matching --tls-ca-cert.
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. 🗂️ Base branches to auto review (1)
Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
💬 Comments — automated reviewThe core TLS ServerName feature — flag registration, credential wiring, restore-path fix, profile-list column, and the |
NumaryBot
left a comment
There was a problem hiding this comment.
NumaryBot posted 1 new inline finding.
Summary: #1595 (comment)
…lied Address independent review of PR #1595: - restore subcommands built gRPC credentials by hand, reading only --server/--insecure and silently ignoring --tls-ca-cert and the new --tls-server-name. Route getRestoreClient through the shared GetClientTransportCredentials so restore honors the same persistent flags (and the insecure/CA/server-name mutual-exclusion guards) as every other command — the restore server is a plausible in-pod dial target where --tls-server-name is exactly what's needed. - Extract buildClientTLSConfig so the tls.Config is directly assertable; the TLSWithServerName test now checks ServerName is set rather than only that creds are non-nil (creds.Info().ServerName is deprecated). - profile list now surfaces the TLS SERVER NAME column, matching how --tls-ca-cert is displayed.
|
Addressed findings from an independent review pass (commit f71f411):
|
NumaryBot
left a comment
There was a problem hiding this comment.
NumaryBot posted 1 new inline finding.
Summary: #1595 (comment)
NumaryBot review finding: --tls-server-name was missing from ledgerctlOwnedFlagNames, so bindSubcommandEnv bound the bare TLS_SERVER_NAME env var for subcommands that redeclare the flag locally (profile create). A stray TLS_SERVER_NAME would then silently populate and persist tlsServerName, bypassing the LEDGERCTL_TLS_SERVER_NAME contract and pinning TLS verification to the wrong name. Add it to the owned set (alongside tls-ca-cert) and cover it in TestSubcommandLocalFlagsIgnoreBareEnv.
NumaryBot
left a comment
There was a problem hiding this comment.
NumaryBot posted 2 new inline findings.
Summary: #1595 (comment)
NumaryBot review findings: profile create and the auth login bootstrap path persisted a profile without validating flag compatibility, since the insecure/TLS mutual-exclusion guard only ran inside GetClientTransportCredentials at connection time. A user passing --insecure together with --tls-server-name (or --tls-ca-cert, a pre-existing gap) would store a profile every later command then immediately rejects. Extract the rule into a shared cmdutil.ValidateTLSFlags (single source of truth, covering both --tls-ca-cert and --tls-server-name for consistency) and call it from GetClientTransportCredentials, profile create, and auth login before persisting — failing fast at write time. Add a direct table test for the helper.
NumaryBot
left a comment
There was a problem hiding this comment.
NumaryBot posted 1 new inline finding.
Summary: #1595 (comment)
| cmd.Flags().String("server", "", "gRPC server address (required)") | ||
| cmd.Flags().Bool("insecure", false, "Use insecure connection (no TLS)") | ||
| cmd.Flags().String("tls-ca-cert", "", "Path to CA certificate file (PEM)") | ||
| cmd.Flags().String("tls-server-name", "", "Hostname to verify against the server certificate SANs, overriding the --server host") |
There was a problem hiding this comment.
🟡 [minor] LEDGERCTL_TLS_SERVER_NAME not applied when creating a profile via environment variable
When profile create is invoked without an explicit --tls-server-name flag but with LEDGERCTL_TLS_SERVER_NAME set, the owned-flag exclusion in bindSubcommandEnv means the env var is not read for the local flag, and ResolveConnectionFlags is not called for profile subcommands. The profile is therefore saved without tlsServerName, so subsequent commands that rely on the persisted value fail unless the user repeats --tls-server-name explicitly every time.
Suggestion: Explicitly read LEDGERCTL_TLS_SERVER_NAME (and similarly LEDGERCTL_TLS_CA_CERT) in the profile-create path before persisting the profile, either by calling a shared resolver or by directly checking the prefixed env var and populating the flag default.
There was a problem hiding this comment.
Working as intended — declining this one. ResolveConnectionFlags deliberately skips profile-management commands (see resolve.go: "they define local flags with the same names and must not be contaminated by the active profile or environment variables"), so profile create never inherits any LEDGERCTL_* value — not just LEDGERCTL_TLS_SERVER_NAME but also LEDGERCTL_SERVER, LEDGERCTL_INSECURE, LEDGERCTL_TLS_CA_CERT, etc. --tls-server-name is fully consistent with every other connection flag here. Auto-baking an ambient env var into a newly-created profile would break that contract and risk silent cross-contamination between profiles (an env set for cluster A leaking into a profile meant for cluster B). Users who want env-driven values use them at command time (where ResolveConnectionFlags does apply them), not at profile-create time. No change.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## release/v3.0 #1595 +/- ##
================================================
+ Coverage 74.24% 74.73% +0.48%
================================================
Files 404 430 +26
Lines 42557 45722 +3165
================================================
+ Hits 31598 34171 +2573
- Misses 8116 8502 +386
- Partials 2843 3049 +206
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Problem
On a TLS-enabled cluster,
ledgerctlis unusable when run from inside a pod (e.g.kubectl exec). TLS verification matches the--serverhost against the server certificate's SANs, and the client never setsServerName— so it verifies against whatever host is in--server. The defaultlocalhost:8888fails because the operator-issued cert covers only the in-cluster DNS names (<pod>.<cluster>-headless.<ns>.svc.cluster.local,<cluster>.<ns>.svc.cluster.local) and deliberately neverlocalhost/127.0.0.1.Fix
Add a persistent
--tls-server-nameflag that overrides the verification hostname independently of the dial address — thecurl --resolve/openssl -servernameequivalent. This lets a client dial by IP/loopback while still validating an FQDN-only certificate.The inter-node transport already does exactly this (
connection_pool.gosetscfg.ServerName), so this bringsledgerctlin line with the existing pattern.Details
--tls-server-name, envLEDGERCTL_TLS_SERVER_NAME, profile fieldtlsServerName(full CLI > env > profile > default precedence).tls.Config.ServerNameinGetClientTransportCredentials.--insecure(conflicting intent), matching the existing--tls-ca-certguard — also catches a strayLEDGERCTL_INSECUREleaking in.profile create,auth login(not dropped on re-login), andprofile show.docs/ops/cli.mdglobal-flags table,profile createtable, and a new subsection covering the in-pod use case (dial-FQDN vs dial-by-IP options).Not included
No operator change: the operator already dials the in-cluster FQDN (
podSelfServerAddr,backupServerAddr), so it has no mismatch and doesn't need the flag.Tests
insecure+tls-server-namerejected; TLS-with-server-name succeeds.tlsServerNamethrough the shared resolver.go build ./...,go vet, and all./cmd/ledgerctl/...tests green.