Move lesson source into the teaching structure, align the course path with the learner's Go and DevOps experience, and add the interactive lesson guides. Add advanced DevOps lifecycle study as a separate section. Bound MQTT message work and drain accepted jobs during shutdown; align the backend implementation with the revised course through Section 6.
6.9 KiB
Section 11 — Understand MQTT security boundaries
Continue the same isolated lab. This lesson explains the MQTT security design and gives integration exercises; certificate provisioning depends on your chosen lab hostname. Keep remote deployment separate until these checks work.
1. Trace the checks in order
❓ Question: A device publishes {"device_id":"device-001"} on another device's topic. Which component can establish who sent it, and which component rejects the mismatch?
💬 Answer: The broker authenticates the MQTT client and applies that client's topic ACL before routing. Paho receives the packet and invokes the handler; the application then validates topic and payload identity agreement. The JSON field is a claim, not proof of identity. If the topic is allowed for the authenticated client but the body conflicts, the application rejects it. A prior PUBACK or other MQTT acknowledgement does not get reversed by that application rejection.
TCP → TLS certificate verification → MQTT CONNECT authentication
→ topic authorization → application identity/schema validation
→ permission to perform the business operation
TLS protects the connection and verifies the server identity. A username/password authenticates an MQTT client. ACLs restrict topic actions. Application validation checks the payload. HTTP user authorization determines whether an API caller may command that device. Passing one check never substitutes for the next.
A Client ID is session identity, not a secret. A payload device_id is a claim. The broker normally does not attach the authenticated publisher username to each forwarded PUBLISH, so the backend cannot authenticate the sender merely by reading a JSON field. Bind a unique device credential to that device's permitted topics and validate topic/body agreement.
2. Configure verified TLS in Go
❓ Question: The CA is trusted, but the certificate is for broker.lab and the client connects to 127.0.0.1. Why can verification still fail?
💬 Answer: Trust-chain validation and server-name verification are separate checks. The certificate must include the DNS name or IP the client verifies. Fix the URL or certificate identity; do not disable verification.
Provision a lab CA and server certificate for the exact DNS name or IP used in the client URL. An IP connection needs that IP in the certificate's Subject Alternative Name. Keep the CA private key outside the broker; the broker needs its server key and certificate. Mount files with broker-readable permissions. Configure Mosquitto's TLS listener using certfile and keyfile; require client certificates only when deliberately introducing mutual TLS.
This fragment belongs in your configuration/transport setup and requires crypto/tls, crypto/x509, fmt, and os. caPath is a configured path to the public lab CA certificate and brokerHostname is the validated URL hostname (without a port):
caPEM, err := os.ReadFile(caPath)
if err != nil {
return fmt.Errorf("read lab CA: %w", err)
}
roots := x509.NewCertPool()
if !roots.AppendCertsFromPEM(caPEM) {
return fmt.Errorf("lab CA contains no usable certificate")
}
tlsConfig := &tls.Config{
RootCAs: roots,
ServerName: brokerHostname,
MinVersion: tls.VersionTLS12,
}
Assign this configuration to AutoPaho's TlsCfg and use a tls:// broker URL for the TLS listener. Set ServerName explicitly from the validated URL host so certificate verification also has the intended identity on the pinned proxy transport path. Do not disable verification to make a hostname failure disappear; correct the certificate or URL. Update Lesson 3's URL validation deliberately: its early allowlist accepts only the plaintext mqtt:// scheme.
Server-only TLS can still use the existing MQTT credentials and ACLs. Mutual TLS additionally authenticates a client certificate; mapping certificate identity to topic permissions is another deliberate broker policy. Do not assume enabling encryption creates per-device authorization.
Use the official Mosquitto TLS configuration reference and Go TLS configuration API while provisioning. Match broker options to the pinned 2.0.22 image.
3. Test failures at the intended layer
| Change one input | Expected failure layer | Evidence to collect |
|---|---|---|
| Wrong CA | TLS verification | No authenticated MQTT session established |
| Valid CA, wrong server name | TLS identity verification | Hostname/IP mismatch |
| Valid TLS, wrong MQTT password | CONNECT authentication | Connection error / unsuccessful CONNACK |
| Valid login, other device's publication topic | Topic authorization | Rejection/delivery denial plus authorized observer and broker logs |
| Permitted topic, mismatched body device | Application validation | Rejection before any state mutation |
| Valid API user, unauthorized target device | HTTP business authorization | Denial before creating/publishing a command |
Never fix an ACL test by replacing narrow permissions with #. A successful broad SUBACK alone is not proof of cross-device access: test actual delivery. The device or simulator checks that a command's Response Topic is exactly the response destination authorized for that device before publishing. The command service separately matches the resulting application response to its pending command. Correlation Data identifies the request to match; it does not authorize the destination or authenticate the publisher.
4. Rotate credentials without confusing state
Updating a password file affects authentication policy; it does not prove every existing connection was disconnected. A fresh connection using the old password must fail, a new one must succeed, and the device must regain its subscriptions. Broker operations explains which credential volume this workspace actually uses and why editing the old host copy does nothing.
Treat MQTT 5 enhanced authentication (AUTH plus an agreed authentication method) as an optional separate extension. It requires compatible client and broker mechanisms; adding User Properties or renaming a username does not enable it. The two-user password-file lab does not demonstrate enhanced authentication.
Why is TLS with a shared fleet password still insufficient for per-device isolation?
TLS protects traffic, but a shared credential does not distinguish individual devices for authorization. A compromised device can exercise that shared identity's permissions. Use separate identities and topic policies, then validate application claims independently.
Done when: explain every boundary, run the failure matrix against the lab, and show that a denied user or device cannot produce a simulated effect. Continue with Section 12 in COURSE.md; the advanced MQTT lesson belongs after the persistence/process exercises.