Go MQTT Vending Backend ยท Lesson 11 ยท 12 minutes
Trace security checks from socket to business action
TLS, MQTT credentials, topic ACLs, payload validation, and HTTP user permissions answer different questions.
โ Start with the system story
A client with a valid username publishes to another device's topic and claims that device's ID in JSON. Which component can reject the topic access, and which component checks the body claim?
Pause and trace the request from connection setup through business authorization.
๐ The checks run in sequence
server identity + encryption
client authentication
topic authorization
contract + business rules
user's device permission
A JSON device_id is a claim, not authenticated identity. A Client ID names a session; it is not a secret. The broker usually does not attach the MQTT publisher username to a forwarded PUBLISH, so bind unique client credentials to narrow topic ACLs, then validate topic/body agreement in the application.
๐ Verify the broker before sending credentials
For TLS, the certificate chain must lead to a trusted CA and its subject alternative name must match the host/IP used by the client. A trusted certificate for broker.lab can still fail when the client verifies 127.0.0.1. Correct the URL or certificate identity; never turn off certificate verification to make the experiment pass.
Server TLS encrypts the channel and verifies the broker. Username/password authenticates the MQTT client. Per-device ACLs authorize topics. Mutual TLS adds client-certificate authentication only when the broker maps that identity to a deliberate policy. These choices do not authorize an HTTP user to operate a particular vending machine.
๐ Complete new file: internal/mqtt/tls.go
File overview: This file reads a public CA certificate and builds a client TLS configuration that verifies both the chain and broker hostname. It does not load client private keys or disable verification.
NewVerifiedTLSConfig summary: Rejects empty inputs and invalid PEM, then returns TLS 1.2+ settings with an explicit ServerName.
Details: The caller supplies the DNS name or IP address used by the broker certificate SAN. This lesson's implementation is deliberately a complete, standalone trust-config helper; it does not replace the existing MQTT client or config loader. The CA file is public trust material; never embed a private CA key in the client.
๐งฉ Full file contents
package mqtt
import (
"crypto/tls"
"crypto/x509"
"fmt"
"os"
"strings"
)
// NewVerifiedTLSConfig builds a client config that verifies the broker's
// certificate chain and the exact DNS name or IP used by the client.
func NewVerifiedTLSConfig(caPath, serverName string) (*tls.Config, error) {
if strings.TrimSpace(caPath) == "" {
return nil, fmt.Errorf("CA certificate path is required")
}
if strings.TrimSpace(serverName) == "" {
return nil, fmt.Errorf("broker server name is required")
}
caPEM, err := os.ReadFile(caPath)
if err != nil {
return nil, fmt.Errorf("read broker CA: %w", err)
}
roots := x509.NewCertPool()
if !roots.AppendCertsFromPEM(caPEM) {
return nil, fmt.Errorf("broker CA file contains no valid certificates")
}
return &tls.Config{
RootCAs: roots,
ServerName: serverName,
MinVersion: tls.VersionTLS12,
}, nil
}
Integration exercise: In the existing autopaho.ClientConfig, pass this helper's result through TlsCfg and make the configured broker URL and certificate SAN name agree. Treat changes to the client and config loader as separate files: show each whole updated file in your work, including imports and validation, rather than pasting a partial field diff. Do not use InsecureSkipVerify. Until both files are updated, this helper alone does not make the running client use TLS.
๐งช Change one input at a time
| Change | Expected boundary | Evidence |
|---|---|---|
| Wrong CA | TLS trust | No authenticated MQTT session |
| Wrong certificate hostname | TLS server identity | Name mismatch |
| Wrong password | CONNECT authentication | Connection rejected |
| Valid login, cross-device topic | Broker ACL | Denied publish/subscribe and broker evidence |
| Allowed topic, body ID mismatch | Application validation | Rejected before state mutation |
| Valid HTTP user, unauthorized target | API authorization | Denied before command creation/publication |
๐ฌ Response Topic is also a trust boundary
The simulator should compare a command's Response Topic with its exact authorized response topic before publishing. Correlation Data helps match a response to a request; it does not authorize the destination or authenticate the publisher. An application-level denial also does not reverse an MQTT acknowledgement already sent.
Rotate credentials by verifying fresh connections with both old and new credentials and checking subscription restoration. Updating a password file alone does not prove existing connections were closed.
โ Done when
Run the negative matrix in the disposable lab and show that each failure is rejected at its intended boundary. A broad # ACL is not a fix for an authorization failure.
๐ Short self-check answers
- Broker ACL: which authenticated MQTT client may use which topic.
- Application: whether the delivered topic and payload claims agree and whether the operation is valid.
- API authorization: whether this HTTP principal may act on the requested device.
๐ Full failure matrix: Section 11 source lesson. Primary references: Go TLS Config API, Mosquitto configuration manual, and OASIS MQTT 5.0 Standard.
Ask me to inspect a TLS or ACL failure with you; the layer and evidence should agree.