diff --git a/docs/authorization.md b/docs/authorization.md index 39a90943..18f2fa9b 100644 --- a/docs/authorization.md +++ b/docs/authorization.md @@ -344,8 +344,7 @@ Users with viewer relations have access to view all entities (things, channels, ### Domain Member -Users with member relations could not view and no access to entities (things, channels, groups) created by others within the domain. Members are also allowed to create their own entities and can view and update the ones they have created. -Domain members will not have access by default to any of the entities in the Domain, access shall be granted for specific entities by the domain administrator or individual entity administrator. +Users with ```member``` relation in a domain do not have default access to view or interact with entities (such as things, channels, or groups) created by others in the same domain. However, these members are permitted to create their own entities and have full access to view and update those they have personally created. Despite this, ```members``` do not automatically gain access to all entities within the domain. Instead, access to specific entities must be explicitly granted by either the domain administrator or the individual administrator of the entity in question. **Example:** **user_4 , user_5, user_6, user_7, user_8, user_9** is **member** of **domain_1**. **These member relation users can able to create & manage new things, channels & groups in the domain. They can have access to the entities to which they have a relation in the domain. They could not view and manage other entities to which they don't have any relation in domain**. diff --git a/docs/messaging.md b/docs/messaging.md index a1e10ef9..e94cba1f 100644 --- a/docs/messaging.md +++ b/docs/messaging.md @@ -1,13 +1,69 @@ # Messaging -Once a channel is provisioned and thing is connected to it, it can start to publish messages on the channel. The following sections will provide an example of message publishing for each of the supported protocols. +Once a channel is provisioned and thing is connected to it, it can start to publish messages on the channel. The following sections will provide an example of message publishing for each of the supported protocols, with the examples being shown without TLS, with TLS, and with mTLS. +> While using MTLS the `thing.crt` and `thing.crt` can be genarated by running `make thing_cert` while in the `docker/ssl` folder ## HTTP +The following environmental variables are used to enable or disable HTTP with TLS and MTLS: `MG_HTTP_ADAPTER_CERT_FILE`,`MG_HTTP_ADAPTER_KEY_FILE`, `MG_HTTP_ADAPTER_SERVER_CA_FILE`, `MG_HTTP_ADAPTER_CLIENT_CA_FILE`. These can be located in the `docker/.env` file. By default magistrala is set to run without TLS or MTLS enabled. + +### Without TLS + +To publish message over channel, thing should send following request: + +```bash +curl -s -S -i -X POST -H "Content-Type: application/senml+json" -H "Authorization: Thing " http://localhost/http/channels//messages -d '[{"bn":"some-base-name:","bt":1.276020076001e+09, "bu":"A","bver":5, "n":"voltage","u":"V","v":120.1}, {"n":"current","t":-5,"v":1.2}, {"n":"current","t":-4,"v":1.3}]' +``` +The response body should look something like: +```bash +HTTP/1.1 202 Accepted +Server: nginx/1.25.4 +Date: Wed, 22 May 2024 16:59:26 GMT +Content-Type: application/json +Content-Length: 0 +Connection: keep-alive +``` + +### With TLS + +To use magistala HTTP with TLS, set the following environment variables as follows: +```bash +MG_HTTP_ADAPTER_CERT_FILE=./ssl/certs/magistrala-server.crt +MG_HTTP_ADAPTER_KEY_FILE=./ssl/certs/magistrala-server.key +``` +Then update the line `proxy_pass http://http-adapter:${MG_HTTP_ADAPTER_PORT}/;` to `proxy_pass https://http-adapter:${MG_HTTP_ADAPTER_PORT}/;` in `docker/nginx/nginx-key.conf`.Then restart magistrala. To publish message over channel, thing should send following request: ```bash curl -s -S -i --cacert docker/ssl/certs/ca.crt -X POST -H "Content-Type: application/senml+json" -H "Authorization: Thing " https://localhost/http/channels//messages -d '[{"bn":"some-base-name:","bt":1.276020076001e+09, "bu":"A","bver":5, "n":"voltage","u":"V","v":120.1}, {"n":"current","t":-5,"v":1.2}, {"n":"current","t":-4,"v":1.3}]' +``` +The response body should look something like: +```bash +HTTP/2 202 +server: nginx/1.25.4 +date: Tue, 28 May 2024 09:10:22 GMT +content-type: application/json +content-length: 0 +``` + +### With MTLS + +To use magistala HTTP with MTLS, set the following environment variables as follows and restart magistrala: +```bash +MG_HTTP_ADAPTER_CERT_FILE=./ssl/certs/magistrala-server.crt +MG_HTTP_ADAPTER_KEY_FILE=./ssl/certs/magistrala-server.key +MG_HTTP_ADAPTER_SERVER_CA_FILE=./ssl/certs/ca.crt +MG_HTTP_ADAPTER_CLIENT_CA_FILE=./ssl/certs/ca.crt +``` +To publish message over channel, thing should send following request: + +```bash +curl -s -S -i --cacert docker/ssl/certs/ca.crt --cert docker/ssl/certs/thing.crt --key docker/ssl/certs/thing.key -X POST -H "Content-Type: application/senml+json" -H "Authorization: Thing " https://localhost/http/channels//messages -d '[{"bn":"some-base-name:","bt":1.276020076001e+09, "bu":"A","bver":5, "n":"voltage","u":"V","v":120.1}, {"n":"current","t":-5,"v":1.2}, {"n":"current","t":-4,"v":1.3}]' +``` + +The response body should look something like: +```bash + ``` Note that if you're going to use senml message format, you should always send messages as an array. @@ -17,17 +73,103 @@ For more information about the HTTP messaging service API, please check out the ## MQTT To send and receive messages over MQTT you could use [Mosquitto tools][mosquitto], or [Paho][paho] if you want to use MQTT over WebSocket. +The following environmental variables are used to enable or disable MQTT with TLS and MTLS: `MG_MQTT_ADAPTER_CERT_FILE`, `MG_MQTT_ADAPTER_KEY_FILE`, `MG_MQTT_ADAPTER_SERVER_CA_FILE`, `MG_MQTT_ADAPTER_CLIENT_CA_FILE`. + +### Without TLS -To publish message over channel, thing should call following command: +To publish message over channel, thing should send following request: ```bash -mosquitto_pub -u -P -t channels//messages -h localhost -m '[{"bn":"some-base-name:","bt":1.276020076001e+09, "bu":"A","bver":5, "n":"voltage","u":"V","v":120.1}, {"n":"current","t":-5,"v":1.2}, {"n":"current","t":-4,"v":1.3}]' +mosquitto_pub -i magistrala -u -P -t channels//messages -h localhost -m '[{"bn":"some-base-name:","bt":1.276020076001e+09, "bu":"A","bver":5, "n":"voltage","u":"V","v":120.1}, {"n":"current","t":-5,"v":1.2}, {"n":"current","t":-4,"v":1.3}]' ``` -To subscribe to channel, thing should call following command: +This is the expected logs on magistrala-mqtt: +```bash +{"time":"2024-05-22T17:40:26.460507725Z","level":"INFO","msg":"Accepted new client"} +{"time":"2024-05-22T17:40:26.461947159Z","level":"INFO","msg":"connected with client_id magitrala100398"} +{"time":"2024-05-22T17:40:26.482658303Z","level":"INFO","msg":"published with client_id magitrala100398 to the topic channels/17c1bb45-7dc7-44eb-a2b4-57a16e4bc05b/messages"} +{"time":"2024-05-22T17:40:26.485183131Z","level":"ERROR","msg":"disconnected client_id magitrala100398 and username 91f6e004-9f9e-4f95-9b72-a04befbdf584"} +``` + +To subscribe to channel, thing should send following request: + +```bash +mosquitto_sub -i magistrala -u -P -t channels//messages -h localhost +``` +This is the expected logs on magistrala-mqtt: +```bash +{"time":"2024-05-22T17:34:08.779743250Z","level":"INFO","msg":"Accepted new client"} +{"time":"2024-05-22T17:34:08.846790260Z","level":"INFO","msg":"connected with client_id magistrala93639"} +{"time":"2024-05-22T17:34:08.932790249Z","level":"INFO","msg":"subscribed with client_id magistrala93639 to topics channels/17c1bb45-7dc7-44eb-a2b4-57a16e4bc05b/messages"} +``` +When message is published the message is expected to be seen on the subscriber side like shown: +```bash +[{"bn":"some-base-name:","bt":1.276020076001e+09, "bu":"A","bver":5, "n":"voltage","u":"V","v":120.1}, {"n":"current","t":-5,"v":1.2}, {"n":"current","t":-4,"v":1.3}] +``` + +### With TLS + +To use magistala mqtt with TLS, set the following environment variables: + +```bash +MG_MQTT_ADAPTER_CERT_FILE=./ssl/certs/magistrala-server.crt +MG_MQTT_ADAPTER_KEY_FILE=./ssl/certs/magistrala-server.key +``` +To publish message over channel, thing should send following request: +```bash +mosquitto_pub -i magistrala --cafile docker/ssl/certs/ca.crt -u -P -t channels//messages -h localhost -p 1883 -m '[{"bn":"some-base-name:","bt":1.276020076001e+09, "bu":"A","bver":5, "n":"voltage","u":"V","v":120.1}, {"n":"current","t":-5,"v":1.2}, {"n":"current","t":-4,"v":1.3}]' +``` +This is the expected logs on magistrala-mqtt: ```bash -mosquitto_sub -u -P -t channels//messages -h localhost +{"time":"2024-05-27T07:28:05.550261589Z","level":"INFO","msg":"Accepted new client"} +{"time":"2024-05-27T07:28:05.615021741Z","level":"INFO","msg":"connected with client_id magistrala32808"} +{"time":"2024-05-27T07:28:05.668840909Z","level":"INFO","msg":"published with client_id magistrala32808 to the topic channels/dd5426b2-3ec8-4259-9a07-c2bbb7127ce7/messages"} +{"time":"2024-05-27T07:28:05.669544477Z","level":"ERROR","msg":"disconnected client_id magistrala32808 and username 7abb9fa0-8700-4f9f-9e38-c93dd4bda017"} +``` + +To subscribe to channel, thing should send following request: + +```bash +mosquitto_sub -i magistrala --cafile docker/ssl/certs/ca.crt -u -P -t channels//messages -h localhost -p 1883 +``` +This is the expected logs on magistrala-mqtt: +```bash +{"time":"2024-05-27T07:33:36.517467526Z","level":"INFO","msg":"Accepted new client"} +{"time":"2024-05-27T07:33:36.578444287Z","level":"INFO","msg":"connected with client_id magistrala38318"} +{"time":"2024-05-27T07:33:36.590345981Z","level":"INFO","msg":"subscribed with client_id magistrala38318 to topics channels/dd5426b2-3ec8-4259-9a07-c2bbb7127ce7/messages"} +``` + +### With MTLS + +To use magistala MQTT with MTLS, set the following environment variables as follows and restart magistrala: +```bash +MG_MQTT_ADAPTER_CERT_FILE=./ssl/certs/magistrala-server.crt +MG_MQTT_ADAPTER_KEY_FILE=./ssl/certs/magistrala-server.key +MG_MQTT_ADAPTER_SERVER_CA_FILE=./ssl/certs/ca.crt +MG_MQTT_ADAPTER_CLIENT_CA_FILE=./ssl/certs/ca.crt +``` +To publish message over channel, thing should send following request: +```bash +mosquitto_pub -i magistrala -u -P -t channels//messages -h localhost -p 1883 -m '[{"bn":"some-base-name:","bt":1.276020076001e+09, "bu":"A","bver":5, "n":"voltage","u":"V","v":120.1}, {"n":"current","t":-5,"v":1.2}, {"n":"current","t":-4,"v":1.3}]' --cafile docker/ssl/certs/ca.crt --cert docker/ssl/certs/thing.crt --key docker/ssl/certs/thing.key +``` +This is the expected logs on magistrala-mqtt: +```bash +{"time":"2024-05-27T07:43:25.03963452Z","level":"INFO","msg":"Accepted new client"} +{"time":"2024-05-27T07:43:25.072298607Z","level":"INFO","msg":"connected with client_id magistrala48391"} +{"time":"2024-05-27T07:43:25.092920495Z","level":"INFO","msg":"published with client_id magistrala48391 to the topic channels/dd5426b2-3ec8-4259-9a07-c2bbb7127ce7/messages"} +{"time":"2024-05-27T07:43:25.094117867Z","level":"ERROR","msg":"disconnected client_id magistrala48391 and username 7abb9fa0-8700-4f9f-9e38-c93dd4bda017"} +``` +To subscribe to channel, thing should send following request: + +```bash +mosquitto_sub -i magistrala -u -P -t channels//messages -h localhost -p 1883 --cafile docker/ssl/certs/ca.crt --cert docker/ssl/certs/thing.crt --key docker/ssl/certs/thing.key +``` +This is the expected logs on magistrala-mqtt: +```bash +{"time":"2024-05-27T07:48:27.617045744Z","level":"INFO","msg":"Accepted new client"} +{"time":"2024-05-27T07:48:27.647008937Z","level":"INFO","msg":"connected with client_id magistrala53810"} +{"time":"2024-05-27T07:48:27.660881053Z","level":"INFO","msg":"subscribed with client_id magistrala53810 to topics channels/dd5426b2-3ec8-4259-9a07-c2bbb7127ce7/messages"} ``` If you want to use standard topic such as `channels//messages` with SenML content type (JSON or CBOR), you should use following topic `channels//messages`. @@ -185,6 +327,8 @@ i.e. connection URL should be `ws:///mqtt`. For quick testing you can use [HiveMQ UI tool][websocket-client]. +The following environment variables are used to enable or disable MQTT-over-WS with TLS and MTLS: `MG_MQTT_ADAPTER_WS_CERT_FILE`,`MG_MQTT_ADAPTER_WS_KEY_FILE`, `MG_MQTT_ADAPTER_WS_SERVER_CA_FILE`, `MG_MQTT_ADAPTER_WS_CLIENT_CA_FILE`. + Here is an example of a browser application connecting to Magistrala server and sending and receiving messages over WebSocket using MQTT.js library: ```javascript @@ -246,6 +390,360 @@ client = new Paho.MQTT.Client(loc.hostname, Number(loc.port), "clientId"); client.connect({ onSuccess: onConnect }); ``` +## Examples using Eclipse Paho lib in Go. + +### Without TLS + +Here is an example of a Go program connecting to Magistrala server and sending messages over MQTT without TLS using paho mqtt library: + +```go +package main + +import ( + "crypto/tls" + "fmt" + + mqtt "github.com/eclipse/paho.mqtt.golang" +) + +var ( + brokerAddress = "ws://localhost/mqtt" + thingID = "1255616f-d9ff-4fb4-b2d9-f72a7c99438e" + thingSecret = "91f6e004-9f9e-4f95-9b72-a04befbdf584" + channelID = "17c1bb45-7dc7-44eb-a2b4-57a16e4bc05b" + topic = "channels/" + channelID + "/messages" +) + +func main() { + opts := mqtt.NewClientOptions() + opts.AddBroker(brokerAddress) + opts.SetClientID(thingID) + opts.SetUsername(thingID) + opts.SetPassword(thingSecret) + + client, err := Connect(opts, nil) + if err != nil { + panic(err) + } + + publish(client) + client.Disconnect(250) +} + +func Connect(opts *mqtt.ClientOptions, tlsCfg *tls.Config) (mqtt.Client, error) { + fmt.Println("Connecting to broker") + if tlsCfg != nil { + opts.SetTLSConfig(tlsCfg) + } + + client := mqtt.NewClient(opts) + + if token := client.Connect(); token.Wait() && token.Error() != nil { + fmt.Println("Failed to connect to broker") + return client, token.Error() + } + return client, nil +} + +func publish(client mqtt.Client) { + fmt.Println("Publishing message") + text := "MQTT over WS Demo" + token := client.Publish(topic, 0, false, text) + token.Wait() + if token.Error() != nil { + fmt.Printf("Failed to publish to topic") + panic(token.Error()) + } +} + +``` +This is the expected logs on magistrala-mqtt: +```bash +{"time":"2024-05-28T07:43:33.176947136Z","level":"INFO","msg":"connected with client_id da018f93-49fd-4409-9ede-70c718d336a0"} +{"time":"2024-05-28T07:43:33.259267711Z","level":"INFO","msg":"published with client_id da018f93-49fd-4409-9ede-70c718d336a0 to the topic channels/dd5426b2-3ec8-4259-9a07-c2bbb7127ce7/messages"} +{"time":"2024-05-28T07:43:33.26171184Z","level":"ERROR","msg":"disconnected client_id da018f93-49fd-4409-9ede-70c718d336a0 and username 7abb9fa0-8700-4f9f-9e38-c93dd4bda017"} +``` + + +### With TLS + +To use magistrala MQTT-over-WS with TLS, set the following environment variables as follows and restart magistrala: +```bash +MG_MQTT_ADAPTER_WS_CERT_FILE=./ssl/certs/magistrala-server.crt +MG_MQTT_ADAPTER_WS_KEY_FILE=./ssl/certs/magistrala-server.key +``` +To publish message over channel, thing should send following request: + +```go +package main + +import ( + "crypto/tls" + "crypto/x509" + "errors" + "fmt" + "os" + + mqtt "github.com/eclipse/paho.mqtt.golang" +) + +var ( + brokerAddress = "wss://localhost/mqtt" + thingID = "1255616f-d9ff-4fb4-b2d9-f72a7c99438e" + thingSecret = "91f6e004-9f9e-4f95-9b72-a04befbdf584" + channelID = "17c1bb45-7dc7-44eb-a2b4-57a16e4bc05b" + topic = "channels/" + channelID + "/messages" + certFile = "" + keyFile = "" + serverCAFile = "docker/ssl/certs/ca.crt" + clientCAFile = "" + errLoadCerts = errors.New("failed to load certificates") + errLoadServerCA = errors.New("failed to load Server CA") + errLoadClientCA = errors.New("failed to load Client CA") + errAppendCA = errors.New("failed to append root ca tls.Config") +) + +func main() { + opts := mqtt.NewClientOptions() + opts.AddBroker(brokerAddress) + opts.SetClientID(thingID) + opts.SetUsername(thingID) + opts.SetPassword(thingSecret) + + tlsCfg, err := LoadTLS(certFile, keyFile, serverCAFile, clientCAFile) + if err != nil { + panic(err) + } + + client, err := Connect(opts, tlsCfg) + if err != nil { + panic(err) + } + + publish(client) + client.Disconnect(250) +} + +func Connect(opts *mqtt.ClientOptions, tlsCfg *tls.Config) (mqtt.Client, error) { + if tlsCfg != nil { + opts.SetTLSConfig(tlsCfg) + } + + client := mqtt.NewClient(opts) + + if token := client.Connect(); token.Wait() && token.Error() != nil { + fmt.Println("Failed to connect to broker") + return client, token.Error() + } + return client, nil +} + +func publish(client mqtt.Client) { + text := "MQTT over WS Demo" + token := client.Publish(topic, 0, false, text) + token.Wait() + if token.Error() != nil { + fmt.Printf("Failed to publish to topic") + panic(token.Error()) + } +} + +// LoadTLS return a TLS configuration that can be used in TLS servers +func LoadTLS(certFile, keyFile, serverCAFile, clientCAFile string) (*tls.Config, error) { + tlsConfig := &tls.Config{} + + // Load Certs and Key if available + if certFile != "" || keyFile != "" { + certificate, err := tls.LoadX509KeyPair(certFile, keyFile) + if err != nil { + return nil, errors.Join(errLoadCerts, err) + } + tlsConfig = &tls.Config{ + Certificates: []tls.Certificate{certificate}, + } + tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert + } + + // Load Server CA if available + rootCA, err := loadCertFile(serverCAFile) + if err != nil { + return nil, errors.Join(errLoadServerCA, err) + } + if len(rootCA) > 0 { + if tlsConfig.RootCAs == nil { + tlsConfig.RootCAs = x509.NewCertPool() + } + if !tlsConfig.RootCAs.AppendCertsFromPEM(rootCA) { + return nil, errAppendCA + } + } + + // Load Client CA if available + clientCA, err := loadCertFile(clientCAFile) + if err != nil { + return nil, errors.Join(errLoadClientCA, err) + } + if len(clientCA) > 0 { + if tlsConfig.ClientCAs == nil { + tlsConfig.ClientCAs = x509.NewCertPool() + } + if !tlsConfig.ClientCAs.AppendCertsFromPEM(clientCA) { + return nil, errAppendCA + } + } + return tlsConfig, nil +} + +func loadCertFile(certFile string) ([]byte, error) { + if certFile != "" { + return os.ReadFile(certFile) + } + return []byte{}, nil +} + +``` + +### With MTLS + +To use magistrala MQTT-over-WS with MTLS, set the following environment variables as follows and restart magistrala: +```bash +MG_MQTT_ADAPTER_WS_CERT_FILE=./ssl/certs/magistrala-server.crt +MG_MQTT_ADAPTER_WS_KEY_FILE=./ssl/certs/magistrala-server.key +MG_MQTT_ADAPTER_WS_SERVER_CA_FILE=./ssl/certs/ca.crt +MG_MQTT_ADAPTER_WS_CLIENT_CA_FILE=./ssl/certs/ca.crt +``` +To publish message over channel, thing should send following request: + +```go +package main + +import ( + "crypto/tls" + "crypto/x509" + "errors" + "fmt" + "os" + + mqtt "github.com/eclipse/paho.mqtt.golang" +) + +var ( + brokerAddress = "wss://localhost/mqtt" + thingID = "1255616f-d9ff-4fb4-b2d9-f72a7c99438e" + thingSecret = "91f6e004-9f9e-4f95-9b72-a04befbdf584" + channelID = "17c1bb45-7dc7-44eb-a2b4-57a16e4bc05b" + topic = "channels/" + channelID + "/messages" + certFile = "ssl/certs/thing.crt" + keyFile = "ssl/certs/thing.key" + serverCAFile = "ssl/certs/ca.crt" + clientCAFile = "" + errLoadCerts = errors.New("failed to load certificates") + errLoadServerCA = errors.New("failed to load Server CA") + errLoadClientCA = errors.New("failed to load Client CA") + errAppendCA = errors.New("failed to append root ca tls.Config") +) + +func main() { + opts := mqtt.NewClientOptions() + opts.AddBroker(brokerAddress) + opts.SetClientID(thingID) + opts.SetUsername(thingID) + opts.SetPassword(thingSecret) + + tlsCfg, err := LoadTLS(certFile, keyFile, serverCAFile, clientCAFile) + if err != nil { + panic(err) + } + + client, err := Connect(opts, tlsCfg) + if err != nil { + panic(err) + } + + publish(client) + client.Disconnect(250) +} + +func Connect(opts *mqtt.ClientOptions, tlsCfg *tls.Config) (mqtt.Client, error) { + if tlsCfg != nil { + opts.SetTLSConfig(tlsCfg) + } + + client := mqtt.NewClient(opts) + + if token := client.Connect(); token.Wait() && token.Error() != nil { + fmt.Println("Failed to connect to broker") + return client, token.Error() + } + return client, nil +} + +func publish(client mqtt.Client) { + text := "MQTT over WS Demo" + token := client.Publish(topic, 0, false, text) + token.Wait() + if token.Error() != nil { + fmt.Printf("Failed to publish to topic") + panic(token.Error()) + } +} + +// Load return a TLS configuration that can be used in TLS servers +func LoadTLS(certFile, keyFile, serverCAFile, clientCAFile string) (*tls.Config, error) { + tlsConfig := &tls.Config{} + + // Load Certs and Key if available + if certFile != "" || keyFile != "" { + certificate, err := tls.LoadX509KeyPair(certFile, keyFile) + if err != nil { + return nil, errors.Join(errLoadCerts, err) + } + tlsConfig = &tls.Config{ + Certificates: []tls.Certificate{certificate}, + } + tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert + } + + // Load Server CA if available + rootCA, err := loadCertFile(serverCAFile) + if err != nil { + return nil, errors.Join(errLoadServerCA, err) + } + if len(rootCA) > 0 { + if tlsConfig.RootCAs == nil { + tlsConfig.RootCAs = x509.NewCertPool() + } + if !tlsConfig.RootCAs.AppendCertsFromPEM(rootCA) { + return nil, errAppendCA + } + } + + // Load Client CA if available + clientCA, err := loadCertFile(clientCAFile) + if err != nil { + return nil, errors.Join(errLoadClientCA, err) + } + if len(clientCA) > 0 { + if tlsConfig.ClientCAs == nil { + tlsConfig.ClientCAs = x509.NewCertPool() + } + if !tlsConfig.ClientCAs.AppendCertsFromPEM(clientCA) { + return nil, errAppendCA + } + } + return tlsConfig, nil +} + +func loadCertFile(certFile string) ([]byte, error) { + if certFile != "" { + return os.ReadFile(certFile) + } + return []byte{}, nil +} + + +``` + ## Subtopics In order to use subtopics and give more meaning to your pub/sub channel, you can simply add any suffix to base `/channels//messages` topic.