Skip to content

client: tcp+tls with client certificates (--tls-cert, --tls-key, --tls-ca) - #3

Merged
boeboe merged 2 commits into
mainfrom
feat/client-tls
Oct 15, 2026
Merged

boeboe merged 2 commits into
mainfrom
feat/client-tls

Conversation

@boeboe

@boeboe boeboe commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Problem

--url tcp+tls://... was accepted but could not connect. go-modbus requires a client certificate and root CAs for Modbus/TCP Security, and modbusctl never set Config.TLSClientCert or TLSRootCAs.

Change

  • New config.TLSConfig with --tls-cert, --tls-key and --tls-ca (MODBUSCTL_TLS_CERT, MODBUSCTL_TLS_KEY, MODBUSCTL_TLS_CA). It is embedded next to SerialConfig in every config that dials: read, decode, write, poll, scan, record, identify, reportserverid, fingerprint, diagnostic and every sunspec command. Other URLs ignore it.
    • --tls-key defaults to the --tls-cert file, which may hold both. --tls-ca defaults to the system roots and is loaded with go-modbus LoadCertPool.
  • Validation loads the files for tcp+tls://. A missing certificate, a mismatched key or a bad CA file is invalid input (exit 3), and nothing is sent.
  • An untrusted server, or a server that rejects the client certificate, is a transport error (exit 4).
  • buildDeviceClientConfig, openDeviceLink and openRecordingLink now take the TLS settings and apply them.
  • README (transport table, a Modbus/TCP Security section, env table), help examples for read, scan and record, and RELEASE.md (v3.2.0, unreleased).

Not included

  • No --tls-server-name. go-modbus v1.7.0 has no setting for it; it checks the device certificate against the URL host.
  • No TLS on server static and server replay. They still serve plain Modbus TCP.

Tests

  • mbtest.NewPKI: a throwaway CA, server and client certificates written as PEM files. Device.ServeTLS adds MBAP over TLS with client authentication required.
  • internal/modbus/e2e_tls_test.go:
    • Record a go-modbus tcp+tls server (FC03 counter, FC08, FC17, FC20, FC24). The capture's target transport is tls.
    • Replay the capture with server replay: the values arrive in order and FC17 matches.
    • Serve the capture back from a go-modbus tcp+tls server and read it with the TLS client.
    • Every client command over TLS, including identify with parallel units.
    • Negative cases: a CA from another PKI (x509 unknown authority, no capture left behind), the system roots, a server certificate without the IP SAN, a client certificate the server does not trust (rejected, not timed out, device untouched), and missing or bad files.
  • cmd/client/tls_test.go: TLS flags on every command that has --url, the help text, client read and client record over TLS, and CLI exit codes.
  • Config: the TLS env vars are loaded for every dialing config. Validation table cases added.
  • make check passes, with coverage at 96.2%.

One behaviour to be aware of: with TLS 1.3, a server that rejects the client certificate only reports it on the first request. client read/record have already created the capture file by then, as for any connection that fails after connecting. An untrusted server is caught at dial time and leaves no file.

  • Error paths: unusable TLS files on a tcp+tls:// URL are invalid input in every client path (device and recording links, fingerprint, identify, report server ID, SunSpec), also when a caller skips CLI validation. TLSConfig.Load is tested for every kind of unusable file. Patch coverage is 94.4%; the rest are t.Fatalf branches of the test PKI helper.

🤖 Generated with Claude Code

@codecov

codecov Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 89.61039% with 16 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
internal/mbtest/tls.go 78.94% 8 Missing and 8 partials ⚠️

📢 Thoughts on this report? Let us know!

boeboe and others added 2 commits October 15, 2026 09:25
…s-ca)

tcp+tls:// URLs were accepted but could not connect: go-modbus requires a
client certificate and root CAs for Modbus/TCP Security, and modbusctl never
set Config.TLSClientCert or TLSRootCAs.

Every client command now takes --tls-cert, --tls-key and --tls-ca
(MODBUSCTL_TLS_CERT, MODBUSCTL_TLS_KEY, MODBUSCTL_TLS_CA), embedded next to
the serial settings and ignored for other URLs. The key defaults to the
certificate file and the CA to the system roots. Unusable files are invalid
input before anything is sent; an untrusted server or a rejected client
certificate is a transport error.

Tests: a throwaway PKI and a TLS listener in mbtest; record a go-modbus
tcp+tls server, replay the capture over TCP and serve it back over TLS; every
client command over TLS; untrusted server, untrusted client, missing and bad
files; CLI flags, help and environment.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Unusable TLS files on a tcp+tls:// URL are invalid input in every
client path (device and recording links, fingerprint, identify, report
server ID, SunSpec), also when a caller skips the CLI validation; and
TLSConfig.Load reports each kind of unusable file, including system
roots that cannot be read (through a seam).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@boeboe
boeboe changed the base branch from feat/serve-all-functions to main October 15, 2026 07:30
@boeboe
boeboe merged commit b04ef0b into main Oct 15, 2026
7 checks passed
@boeboe
boeboe deleted the feat/client-tls branch October 15, 2026 07:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant