From 9eed3a40f913684ba70196e3c7dc646729b414fa Mon Sep 17 00:00:00 2001 From: Rodney Osodo Date: Thu, 6 Feb 2025 19:15:05 +0300 Subject: [PATCH 01/13] Revert "NOISSUE - Use NATS as MQTT broker (#2681)" This reverts commit cff6e7f0858c07fffdeafccf6a73f8a87c7d9e45. Signed-off-by: Rodney Osodo --- .github/workflows/api-tests.yml | 2 +- .github/workflows/build.yml | 2 +- .github/workflows/check-generated-files.yml | 2 +- .github/workflows/check-license.yaml | 2 +- .github/workflows/tests.yml | 4 +- cmd/mqtt/main.go | 67 ++- docker/.env | 21 +- docker/README.md | 36 +- docker/docker-compose.yml | 17 + docker/vernemq/Dockerfile | 56 +++ docker/vernemq/bin/vernemq.sh | 352 +++++++++++++++ docker/vernemq/files/vm.args | 15 + go.mod | 28 +- go.sum | 53 +-- mqtt/README.md | 16 +- mqtt/forwarder.go | 75 ++++ mqtt/tracing/doc.go | 12 + mqtt/tracing/forwarder.go | 63 +++ pkg/messaging/mqtt/docs.go | 11 + pkg/messaging/mqtt/publisher.go | 61 +++ pkg/messaging/mqtt/pubsub.go | 230 ++++++++++ pkg/messaging/mqtt/pubsub_test.go | 474 ++++++++++++++++++++ pkg/messaging/mqtt/setup_test.go | 121 +++++ 23 files changed, 1640 insertions(+), 80 deletions(-) create mode 100644 docker/vernemq/Dockerfile create mode 100755 docker/vernemq/bin/vernemq.sh create mode 100644 docker/vernemq/files/vm.args create mode 100644 mqtt/forwarder.go create mode 100644 mqtt/tracing/doc.go create mode 100644 mqtt/tracing/forwarder.go create mode 100644 pkg/messaging/mqtt/docs.go create mode 100644 pkg/messaging/mqtt/publisher.go create mode 100644 pkg/messaging/mqtt/pubsub.go create mode 100644 pkg/messaging/mqtt/pubsub_test.go create mode 100644 pkg/messaging/mqtt/setup_test.go diff --git a/.github/workflows/api-tests.yml b/.github/workflows/api-tests.yml index cf999d40b2..b68ded91ab 100644 --- a/.github/workflows/api-tests.yml +++ b/.github/workflows/api-tests.yml @@ -46,7 +46,7 @@ jobs: - name: Install Go uses: actions/setup-go@v5 with: - go-version: 1.23.x + go-version: 1.22.x cache-dependency-path: "go.sum" - name: Build images diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b33514f887..b60a6ec4ea 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -24,7 +24,7 @@ jobs: - name: Install Go uses: actions/setup-go@v5 with: - go-version: 1.23.x + go-version: 1.22.x cache-dependency-path: "go.sum" - name: Run tests diff --git a/.github/workflows/check-generated-files.yml b/.github/workflows/check-generated-files.yml index caf9ab5ce9..a2998b657f 100644 --- a/.github/workflows/check-generated-files.yml +++ b/.github/workflows/check-generated-files.yml @@ -21,7 +21,7 @@ jobs: - name: Install Go uses: actions/setup-go@v5 with: - go-version: 1.23.x + go-version: 1.22.x cache-dependency-path: "go.sum" - name: Check for changes in go.mod diff --git a/.github/workflows/check-license.yaml b/.github/workflows/check-license.yaml index 2977dd9b1e..7b97d2b86b 100644 --- a/.github/workflows/check-license.yaml +++ b/.github/workflows/check-license.yaml @@ -21,7 +21,7 @@ jobs: - name: Check License Header run: | CHECK="" - for file in $(grep -rl --exclude-dir={.git,build} \ + for file in $(grep -rl --exclude-dir={.git,build,**vernemq**} \ --exclude=\*.{crt,key,pem,zed,hcl,md,json,csv,mod,sum,tmpl,args} \ --exclude={CODEOWNERS,LICENSE,MAINTAINERS} \ .); do diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index fe0049e673..2cd96682f9 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -20,7 +20,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v5 with: - go-version: 1.23.x + go-version: 1.22.x cache-dependency-path: "go.sum" - name: Install protolint @@ -63,7 +63,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v5 with: - go-version: 1.23.x + go-version: 1.22.x cache-dependency-path: "go.sum" - name: Check for changes in specific paths diff --git a/cmd/mqtt/main.go b/cmd/mqtt/main.go index 0e713513b4..32bd0353c7 100644 --- a/cmd/mqtt/main.go +++ b/cmd/mqtt/main.go @@ -26,6 +26,7 @@ import ( smqlog "github.com/absmach/supermq/logger" "github.com/absmach/supermq/mqtt" "github.com/absmach/supermq/mqtt/events" + mqtttracing "github.com/absmach/supermq/mqtt/tracing" "github.com/absmach/supermq/pkg/errors" "github.com/absmach/supermq/pkg/grpcclient" jaegerclient "github.com/absmach/supermq/pkg/jaeger" @@ -33,6 +34,7 @@ import ( brokerstracing "github.com/absmach/supermq/pkg/messaging/brokers/tracing" msgevents "github.com/absmach/supermq/pkg/messaging/events" "github.com/absmach/supermq/pkg/messaging/handler" + mqttpub "github.com/absmach/supermq/pkg/messaging/mqtt" "github.com/absmach/supermq/pkg/server" "github.com/absmach/supermq/pkg/uuid" "github.com/caarlos0/env/v11" @@ -48,21 +50,24 @@ const ( ) type config struct { - LogLevel string `env:"SMQ_MQTT_ADAPTER_LOG_LEVEL" envDefault:"info"` - MQTTPort string `env:"SMQ_MQTT_ADAPTER_MQTT_PORT" envDefault:"1883"` - MQTTTargetHost string `env:"SMQ_MQTT_ADAPTER_MQTT_TARGET_HOST" envDefault:"localhost"` - MQTTTargetPort string `env:"SMQ_MQTT_ADAPTER_MQTT_TARGET_PORT" envDefault:"1883"` - MQTTTargetHealthCheck string `env:"SMQ_MQTT_ADAPTER_MQTT_TARGET_HEALTH_CHECK" envDefault:""` - HTTPPort string `env:"SMQ_MQTT_ADAPTER_WS_PORT" envDefault:"8080"` - HTTPTargetHost string `env:"SMQ_MQTT_ADAPTER_WS_TARGET_HOST" envDefault:"localhost"` - HTTPTargetPort string `env:"SMQ_MQTT_ADAPTER_WS_TARGET_PORT" envDefault:"8080"` - Instance string `env:"SMQ_MQTT_ADAPTER_INSTANCE" envDefault:""` - JaegerURL url.URL `env:"SMQ_JAEGER_URL" envDefault:"http://localhost:4318/v1/traces"` - BrokerURL string `env:"SMQ_MESSAGE_BROKER_URL" envDefault:"nats://localhost:4222"` - SendTelemetry bool `env:"SMQ_SEND_TELEMETRY" envDefault:"true"` - InstanceID string `env:"SMQ_MQTT_ADAPTER_INSTANCE_ID" envDefault:""` - ESURL string `env:"SMQ_ES_URL" envDefault:"nats://localhost:4222"` - TraceRatio float64 `env:"SMQ_JAEGER_TRACE_RATIO" envDefault:"1.0"` + LogLevel string `env:"SMQ_MQTT_ADAPTER_LOG_LEVEL" envDefault:"info"` + MQTTPort string `env:"SMQ_MQTT_ADAPTER_MQTT_PORT" envDefault:"1883"` + MQTTTargetHost string `env:"SMQ_MQTT_ADAPTER_MQTT_TARGET_HOST" envDefault:"localhost"` + MQTTTargetPort string `env:"SMQ_MQTT_ADAPTER_MQTT_TARGET_PORT" envDefault:"1883"` + MQTTForwarderTimeout time.Duration `env:"SMQ_MQTT_ADAPTER_FORWARDER_TIMEOUT" envDefault:"30s"` + MQTTTargetHealthCheck string `env:"SMQ_MQTT_ADAPTER_MQTT_TARGET_HEALTH_CHECK" envDefault:""` + MQTTQoS uint8 `env:"SMQ_MQTT_ADAPTER_MQTT_QOS" envDefault:"1"` + HTTPPort string `env:"SMQ_MQTT_ADAPTER_WS_PORT" envDefault:"8080"` + HTTPTargetHost string `env:"SMQ_MQTT_ADAPTER_WS_TARGET_HOST" envDefault:"localhost"` + HTTPTargetPort string `env:"SMQ_MQTT_ADAPTER_WS_TARGET_PORT" envDefault:"8080"` + HTTPTargetPath string `env:"SMQ_MQTT_ADAPTER_WS_TARGET_PATH" envDefault:"/mqtt"` + Instance string `env:"SMQ_MQTT_ADAPTER_INSTANCE" envDefault:""` + JaegerURL url.URL `env:"SMQ_JAEGER_URL" envDefault:"http://localhost:4318/v1/traces"` + BrokerURL string `env:"SMQ_MESSAGE_BROKER_URL" envDefault:"nats://localhost:4222"` + SendTelemetry bool `env:"SMQ_SEND_TELEMETRY" envDefault:"true"` + InstanceID string `env:"SMQ_MQTT_ADAPTER_INSTANCE_ID" envDefault:""` + ESURL string `env:"SMQ_ES_URL" envDefault:"nats://localhost:4222"` + TraceRatio float64 `env:"SMQ_JAEGER_TRACE_RATIO" envDefault:"1.0"` } func main() { @@ -121,6 +126,38 @@ func main() { }() tracer := tp.Tracer(svcName) + bsub, err := brokers.NewPubSub(ctx, cfg.BrokerURL, logger) + if err != nil { + logger.Error(fmt.Sprintf("failed to connect to message broker: %s", err)) + exitCode = 1 + return + } + defer bsub.Close() + bsub = brokerstracing.NewPubSub(serverConfig, tracer, bsub) + + mpub, err := mqttpub.NewPublisher(fmt.Sprintf("mqtt://%s:%s", cfg.MQTTTargetHost, cfg.MQTTTargetPort), cfg.MQTTQoS, cfg.MQTTForwarderTimeout) + if err != nil { + logger.Error(fmt.Sprintf("failed to create MQTT publisher: %s", err)) + exitCode = 1 + return + } + defer mpub.Close() + + mpub, err = msgevents.NewPublisherMiddleware(ctx, mpub, cfg.ESURL) + if err != nil { + logger.Error(fmt.Sprintf("failed to create event store middleware: %s", err)) + exitCode = 1 + return + } + + fwd := mqtt.NewForwarder(brokers.SubjectAllChannels, logger) + fwd = mqtttracing.New(serverConfig, tracer, fwd, brokers.SubjectAllChannels) + if err := fwd.Forward(ctx, svcName, bsub, mpub); err != nil { + logger.Error(fmt.Sprintf("failed to forward message broker messages: %s", err)) + exitCode = 1 + return + } + np, err := brokers.NewPublisher(ctx, cfg.BrokerURL) if err != nil { logger.Error(fmt.Sprintf("failed to connect to message broker: %s", err)) diff --git a/docker/.env b/docker/.env index 7e006a36e3..b53d986f99 100644 --- a/docker/.env +++ b/docker/.env @@ -20,6 +20,7 @@ SMQ_NATS_URL=nats://nats:${SMQ_NATS_PORT} # Configs for nats as MQTT broker SMQ_NATS_HEALTH_CHECK=http://nats:${SMQ_NATS_HTTP_PORT}/healthz SMQ_NATS_WS_TARGET_PATH= +SMQ_NATS_MQTT_QOS=1 ## RabbitMQ SMQ_RABBITMQ_PORT=5672 @@ -34,14 +35,23 @@ SMQ_RABBITMQ_URL=amqp://${SMQ_RABBITMQ_USER}:${SMQ_RABBITMQ_PASS}@rabbitmq:${SMQ SMQ_MESSAGE_BROKER_TYPE=nats SMQ_MESSAGE_BROKER_URL=${SMQ_NATS_URL} +## VERNEMQ +SMQ_DOCKER_VERNEMQ_ALLOW_ANONYMOUS=on +SMQ_DOCKER_VERNEMQ_LOG__CONSOLE__LEVEL=error +SMQ_VERNEMQ_HEALTH_CHECK=http://vernemq:8888/health +SMQ_VERNEMQ_WS_TARGET_PATH=/mqtt +SMQ_VERNEMQ_MQTT_QOS=2 + ## MQTT Broker -SMQ_MQTT_BROKER_TYPE=nats -SMQ_MQTT_BROKER_HEALTH_CHECK=${SMQ_NATS_HEALTH_CHECK} +SMQ_MQTT_BROKER_TYPE=vernemq +SMQ_MQTT_BROKER_HEALTH_CHECK=${SMQ_VERNEMQ_HEALTH_CHECK} +SMQ_MQTT_ADAPTER_MQTT_QOS=${SMQ_VERNEMQ_MQTT_QOS} SMQ_MQTT_ADAPTER_MQTT_TARGET_HOST=${SMQ_MQTT_BROKER_TYPE} SMQ_MQTT_ADAPTER_MQTT_TARGET_PORT=1883 SMQ_MQTT_ADAPTER_MQTT_TARGET_HEALTH_CHECK=${SMQ_MQTT_BROKER_HEALTH_CHECK} SMQ_MQTT_ADAPTER_WS_TARGET_HOST=${SMQ_MQTT_BROKER_TYPE} SMQ_MQTT_ADAPTER_WS_TARGET_PORT=8080 +SMQ_MQTT_ADAPTER_WS_TARGET_PATH=${SMQ_VERNEMQ_WS_TARGET_PATH} ## Redis SMQ_REDIS_TCP_PORT=6379 @@ -60,7 +70,7 @@ SMQ_JAEGER_TRACE_RATIO=1.0 SMQ_JAEGER_MEMORY_MAX_TRACES=5000 ## Call home -SMQ_SEND_TELEMETRY=false +SMQ_SEND_TELEMETRY=true ## Postgres SMQ_POSTGRES_MAX_CONNECTIONS=100 @@ -283,7 +293,7 @@ SMQ_CLIENTS_INSTANCE_ID= #### Clients Client Config SMQ_CLIENTS_URL=http://clients:9006 SMQ_CLIENTS_AUTH_GRPC_URL=clients:7006 -SMQ_CLIENTS_AUTH_GRPC_TIMEOUT=300s +SMQ_CLIENTS_AUTH_GRPC_TIMEOUT=1s SMQ_CLIENTS_AUTH_GRPC_CLIENT_CERT=${GRPC_MTLS:+./ssl/certs/clients-grpc-client.crt} SMQ_CLIENTS_AUTH_GRPC_CLIENT_KEY=${GRPC_MTLS:+./ssl/certs/clients-grpc-client.key} SMQ_CLIENTS_AUTH_GRPC_CLIENT_CA_CERTS=${GRPC_MTLS:+./ssl/certs/ca.crt} @@ -311,7 +321,7 @@ SMQ_CHANNELS_INSTANCE_ID= #### Channels Client Config SMQ_CHANNELS_URL=http://channels:9005 SMQ_CHANNELS_GRPC_URL=channels:7005 -SMQ_CHANNELS_GRPC_TIMEOUT=300s +SMQ_CHANNELS_GRPC_TIMEOUT=1s SMQ_CHANNELS_GRPC_CLIENT_CERT=${GRPC_MTLS:+./ssl/certs/channels-grpc-client.crt} SMQ_CHANNELS_GRPC_CLIENT_KEY=${GRPC_MTLS:+./ssl/certs/channels-grpc-client.key} SMQ_CHANNELS_GRPC_CLIENT_CA_CERTS=${GRPC_MTLS:+./ssl/certs/ca.crt} @@ -327,6 +337,7 @@ SMQ_HTTP_ADAPTER_INSTANCE_ID= ### MQTT SMQ_MQTT_ADAPTER_LOG_LEVEL=debug SMQ_MQTT_ADAPTER_MQTT_PORT=1883 +SMQ_MQTT_ADAPTER_FORWARDER_TIMEOUT=30s SMQ_MQTT_ADAPTER_WS_PORT=8080 SMQ_MQTT_ADAPTER_INSTANCE= SMQ_MQTT_ADAPTER_INSTANCE_ID= diff --git a/docker/README.md b/docker/README.md index 34d8148c5e..938cab6f1d 100644 --- a/docker/README.md +++ b/docker/README.md @@ -26,21 +26,25 @@ To pull docker images from a specific release you need to change the value of `S SuperMQ supports configurable MQTT broker and Message broker, which also acts as an events store. SuperMQ uses two types of brokers: -1. MQTT_BROKER: Handles MQTT communication between MQTT adapters and message broker. This is NATS. +1. MQTT_BROKER: Handles MQTT communication between MQTT adapters and message broker. This can either be 'VerneMQ' or 'NATS'. 2. MESSAGE_BROKER: Manages message exchange between SuperMQ core, optional, and external services. This can either be 'NATS' or 'RabbitMQ'. This is used to store messages for distributed processing. Events store: This is used by SuperMQ services to store events for distributed processing. SuperMQ uses a single service to be the message broker and events store. This can either be 'NATS' or 'RabbitMQ'. Redis can also be used as an events store, but it requires a message broker to be deployed along with it for message exchange. -This is the same as MESSAGE_BROKER. This can either be 'NATS' or 'RabbitMQ' or 'Redis'. If Redis is used as an events store, then RabbitMQ or NATS is used as a message broker. +This is the same as MESSAGE_BROKER. This can either be 'NATS' or 'RabbitMQ' or 'Redis'. If Redis is used as an events store, then RabbitMQ or NATS is used as a message broker. -The current deployment strategy for SuperMQ in `docker/docker-compose.yml` is to use NATS as a MQTT_BROKER, MESSAGE_BROKER and EVENTS_STORE. +The current deployment strategy for SuperMQ in `docker/docker-compose.yml` is to use VerneMQ as a MQTT_BROKER and NATS as a MESSAGE_BROKER and EVENTS_STORE. Therefore, the following combinations are possible: -- MESSAGE_BROKER: RabbitMQ, EVENTS_STORE: RabbitMQ -- MESSAGE_BROKER: RabbitMQ, EVENTS_STORE: Redis -- MESSAGE_BROKER: NATS, EVENTS_STORE: NATS -- MESSAGE_BROKER: NATS, EVENTS_STORE: Redis +- MQTT_BROKER: VerneMQ, MESSAGE_BROKER: NATS, EVENTS_STORE: NATS +- MQTT_BROKER: VerneMQ, MESSAGE_BROKER: NATS, EVENTS_STORE: Redis +- MQTT_BROKER: VerneMQ, MESSAGE_BROKER: RabbitMQ, EVENTS_STORE: RabbitMQ +- MQTT_BROKER: VerneMQ, MESSAGE_BROKER: RabbitMQ, EVENTS_STORE: Redis +- MQTT_BROKER: NATS, MESSAGE_BROKER: RabbitMQ, EVENTS_STORE: RabbitMQ +- MQTT_BROKER: NATS, MESSAGE_BROKER: RabbitMQ, EVENTS_STORE: Redis +- MQTT_BROKER: NATS, MESSAGE_BROKER: NATS, EVENTS_STORE: NATS +- MQTT_BROKER: NATS, MESSAGE_BROKER: NATS, EVENTS_STORE: Redis For Message brokers other than NATS, you would need to build the docker images with RabbitMQ as the build tag and change the `docker/.env`. For example, to use RabbitMQ as a message broker: @@ -66,6 +70,20 @@ SMQ_ES_TYPE=redis SMQ_ES_URL=${SMQ_REDIS_URL} ``` +For MQTT broker other than VerneMQ, you would need to change the `docker/.env`. For example, to use NATS as a MQTT broker: + +```env +SMQ_MQTT_BROKER_TYPE=nats +SMQ_MQTT_BROKER_HEALTH_CHECK=${SMQ_NATS_HEALTH_CHECK} +SMQ_MQTT_ADAPTER_MQTT_QOS=${SMQ_NATS_MQTT_QOS} +SMQ_MQTT_ADAPTER_MQTT_TARGET_HOST=${SMQ_MQTT_BROKER_TYPE} +SMQ_MQTT_ADAPTER_MQTT_TARGET_PORT=1883 +SMQ_MQTT_ADAPTER_MQTT_TARGET_HEALTH_CHECK=${SMQ_MQTT_BROKER_HEALTH_CHECK} +SMQ_MQTT_ADAPTER_WS_TARGET_HOST=${SMQ_MQTT_BROKER_TYPE} +SMQ_MQTT_ADAPTER_WS_TARGET_PORT=8080 +SMQ_MQTT_ADAPTER_WS_TARGET_PATH=${SMQ_NATS_WS_TARGET_PATH} +``` + ### RabbitMQ configuration ```yaml @@ -107,9 +125,9 @@ By using environment variables file at `docker/.env` you can modify the below gi `SMQ_NGINX_SERVER_NAME` environmental variable is used to configure nginx directive `server_name`. If environmental variable `SMQ_NGINX_SERVER_NAME` is empty then default value `localhost` will set to `server_name`. -`SMQ_NGINX_SERVER_CERT` environmental variable is used to configure nginx directive `ssl_certificate`. If environmental variable `SMQ_NGINX_SERVER_CERT` is empty then by default server certificate in the path `docker/ssl/certs/supermq-server.crt` will be assigned. +`SMQ_NGINX_SERVER_CERT` environmental variable is used to configure nginx directive `ssl_certificate`. If environmental variable `SMQ_NGINX_SERVER_CERT` is empty then by default server certificate in the path `docker/ssl/certs/supermq-server.crt` will be assigned. -`SMQ_NGINX_SERVER_KEY` environmental variable is used to configure nginx directive `ssl_certificate_key`. If environmental variable `SMQ_NGINX_SERVER_KEY` is empty then by default server certificate key in the path `docker/ssl/certs/supermq-server.key` will be assigned. +`SMQ_NGINX_SERVER_KEY` environmental variable is used to configure nginx directive `ssl_certificate_key`. If environmental variable `SMQ_NGINX_SERVER_KEY` is empty then by default server certificate key in the path `docker/ssl/certs/supermq-server.key` will be assigned. `SMQ_NGINX_SERVER_CLIENT_CA` environmental variable is used to configure nginx directive `ssl_client_certificate`. If environmental variable `SMQ_NGINX_SERVER_CLIENT_CA` is empty then by default certificate in the path `docker/ssl/certs/ca.crt` will be assigned. diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 476cef3bc4..39cbf9772f 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -14,6 +14,7 @@ volumes: supermq-channels-db-volume: supermq-clients-redis-volume: supermq-broker-volume: + supermq-mqtt-broker-volume: supermq-spicedb-db-volume: supermq-auth-db-volume: supermq-pat-db-volume: @@ -885,6 +886,7 @@ services: container_name: supermq-mqtt depends_on: - clients + - vernemq - nats restart: on-failure environment: @@ -892,11 +894,14 @@ services: SMQ_MQTT_ADAPTER_MQTT_PORT: ${SMQ_MQTT_ADAPTER_MQTT_PORT} SMQ_MQTT_ADAPTER_MQTT_TARGET_HOST: ${SMQ_MQTT_ADAPTER_MQTT_TARGET_HOST} SMQ_MQTT_ADAPTER_MQTT_TARGET_PORT: ${SMQ_MQTT_ADAPTER_MQTT_TARGET_PORT} + SMQ_MQTT_ADAPTER_FORWARDER_TIMEOUT: ${SMQ_MQTT_ADAPTER_FORWARDER_TIMEOUT} SMQ_MQTT_ADAPTER_MQTT_TARGET_HEALTH_CHECK: ${SMQ_MQTT_ADAPTER_MQTT_TARGET_HEALTH_CHECK} + SMQ_MQTT_ADAPTER_MQTT_QOS: ${SMQ_MQTT_ADAPTER_MQTT_QOS} SMQ_MQTT_ADAPTER_WS_PORT: ${SMQ_MQTT_ADAPTER_WS_PORT} SMQ_MQTT_ADAPTER_INSTANCE_ID: ${SMQ_MQTT_ADAPTER_INSTANCE_ID} SMQ_MQTT_ADAPTER_WS_TARGET_HOST: ${SMQ_MQTT_ADAPTER_WS_TARGET_HOST} SMQ_MQTT_ADAPTER_WS_TARGET_PORT: ${SMQ_MQTT_ADAPTER_WS_TARGET_PORT} + SMQ_MQTT_ADAPTER_WS_TARGET_PATH: ${SMQ_MQTT_ADAPTER_WS_TARGET_PATH} SMQ_MQTT_ADAPTER_INSTANCE: ${SMQ_MQTT_ADAPTER_INSTANCE} SMQ_ES_URL: ${SMQ_ES_URL} SMQ_CLIENTS_AUTH_GRPC_URL: ${SMQ_CLIENTS_AUTH_GRPC_URL} @@ -1202,6 +1207,18 @@ services: bind: create_host_path: true + vernemq: + image: supermq/vernemq:${SMQ_RELEASE_TAG} + container_name: supermq-vernemq + restart: on-failure + environment: + DOCKER_VERNEMQ_ALLOW_ANONYMOUS: ${SMQ_DOCKER_VERNEMQ_ALLOW_ANONYMOUS} + DOCKER_VERNEMQ_LOG__CONSOLE__LEVEL: ${SMQ_DOCKER_VERNEMQ_LOG__CONSOLE__LEVEL} + networks: + - supermq-base-net + volumes: + - supermq-mqtt-broker-volume:/var/lib/vernemq + nats: image: nats:2.10.9-alpine container_name: supermq-nats diff --git a/docker/vernemq/Dockerfile b/docker/vernemq/Dockerfile new file mode 100644 index 0000000000..76152b1f51 --- /dev/null +++ b/docker/vernemq/Dockerfile @@ -0,0 +1,56 @@ +# Copyright (c) Abstract Machines +# SPDX-License-Identifier: Apache-2.0 + +# Builder +FROM erlang:25.3.2.8-alpine AS builder +RUN apk add --update git build-base bsd-compat-headers openssl-dev snappy-dev curl \ + && git clone -b 1.13.0 https://github.com/vernemq/vernemq \ + && cd vernemq \ + && make -j 16 rel + +# Executor +FROM alpine:3.19 + +COPY --from=builder /vernemq/_build/default/rel / + +RUN apk --no-cache --update --available upgrade && \ + apk add --no-cache ncurses-libs openssl libstdc++ jq curl bash snappy-dev && \ + addgroup --gid 10000 vernemq && \ + adduser --uid 10000 -H -D -G vernemq -h /vernemq vernemq && \ + install -d -o vernemq -g vernemq /vernemq + +# Defaults +ENV DOCKER_VERNEMQ_KUBERNETES_LABEL_SELECTOR="app=vernemq" \ + DOCKER_VERNEMQ_LOG__CONSOLE=console \ + PATH="/vernemq/bin:$PATH" \ + VERNEMQ_VERSION="1.13.0" + +WORKDIR /vernemq + +COPY --chown=10000:10000 bin/vernemq.sh /usr/sbin/start_vernemq +COPY --chown=10000:10000 files/vm.args /vernemq/etc/vm.args + +RUN chown -R 10000:10000 /vernemq && \ + ln -s /vernemq/etc /etc/vernemq && \ + ln -s /vernemq/data /var/lib/vernemq && \ + ln -s /vernemq/log /var/log/vernemq + +# Ports +# 1883 MQTT +# 8883 MQTT/SSL +# 8080 MQTT WebSockets +# 44053 VerneMQ Message Distribution +# 4369 EPMD - Erlang Port Mapper Daemon +# 8888 Health, API, Prometheus Metrics +# 9100 9101 9102 9103 9104 9105 9106 9107 9108 9109 Specific Distributed Erlang Port Range + +EXPOSE 1883 8883 8080 44053 4369 8888 \ + 9100 9101 9102 9103 9104 9105 9106 9107 9108 9109 + + +VOLUME ["/vernemq/log", "/vernemq/data", "/vernemq/etc"] + +HEALTHCHECK CMD vernemq ping | grep -q pong + +USER vernemq +CMD ["start_vernemq"] \ No newline at end of file diff --git a/docker/vernemq/bin/vernemq.sh b/docker/vernemq/bin/vernemq.sh new file mode 100755 index 0000000000..4c990dafd1 --- /dev/null +++ b/docker/vernemq/bin/vernemq.sh @@ -0,0 +1,352 @@ +#!/usr/bin/env sh + +NET_INTERFACE=$(route | grep '^default' | grep -o '[^ ]*$') +NET_INTERFACE=${DOCKER_NET_INTERFACE:-${NET_INTERFACE}} +IP_ADDRESS=$(ip -4 addr show ${NET_INTERFACE} | grep -oE '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' | sed -e "s/^[[:space:]]*//" | head -n 1) +IP_ADDRESS=${DOCKER_IP_ADDRESS:-${IP_ADDRESS}} + +VERNEMQ_ETC_DIR="/vernemq/etc" +VERNEMQ_VM_ARGS_FILE="${VERNEMQ_ETC_DIR}/vm.args" +VERNEMQ_CONF_FILE="${VERNEMQ_ETC_DIR}/vernemq.conf" +VERNEMQ_CONF_LOCAL_FILE="${VERNEMQ_ETC_DIR}/vernemq.conf.local" + +SECRETS_KUBERNETES_DIR="/var/run/secrets/kubernetes.io/serviceaccount" + +# Function to check istio readiness +istio_health() { + cmd=$(curl -s http://localhost:15021/healthz/ready > /dev/null) + status=$? + return $status +} + +# Ensure we have all files and needed directory write permissions +if [ ! -d ${VERNEMQ_ETC_DIR} ]; then + echo "Configuration directory at ${VERNEMQ_ETC_DIR} does not exist, exiting" >&2 + exit 1 +fi +if [ ! -f ${VERNEMQ_VM_ARGS_FILE} ]; then + echo "ls -l ${VERNEMQ_ETC_DIR}" + ls -l ${VERNEMQ_ETC_DIR} + echo "###" >&2 + echo "### Configuration file ${VERNEMQ_VM_ARGS_FILE} does not exist, exiting" >&2 + echo "###" >&2 + exit 1 +fi +if [ ! -w ${VERNEMQ_VM_ARGS_FILE} ]; then + echo "# whoami" + whoami + echo "# ls -l ${VERNEMQ_ETC_DIR}" + ls -l ${VERNEMQ_ETC_DIR} + echo "###" >&2 + echo "### Configuration file ${VERNEMQ_VM_ARGS_FILE} exists, but there are no write permissions! Exiting." >&2 + echo "###" >&2 + exit 1 +fi +if [ ! -s ${VERNEMQ_VM_ARGS_FILE} ]; then + echo "ls -l ${VERNEMQ_ETC_DIR}" + ls -l ${VERNEMQ_ETC_DIR} + echo "###" >&2 + echo "### Configuration file ${VERNEMQ_VM_ARGS_FILE} is empty! This will not work." >&2 + echo "### Exiting now." >&2 + echo "###" >&2 + exit 1 +fi + +# Ensure the Erlang node name is set correctly +if env | grep "DOCKER_VERNEMQ_NODENAME" -q; then + sed -i.bak -r "s/-name VerneMQ@.+/-name VerneMQ@${DOCKER_VERNEMQ_NODENAME}/" ${VERNEMQ_VM_ARGS_FILE} +else + if [ -n "$DOCKER_VERNEMQ_SWARM" ]; then + NODENAME=$(hostname -i) + sed -i.bak -r "s/VerneMQ@.+/VerneMQ@${NODENAME}/" ${VERNEMQ_VM_ARGS_FILE} + else + sed -i.bak -r "s/-name VerneMQ@.+/-name VerneMQ@${IP_ADDRESS}/" ${VERNEMQ_VM_ARGS_FILE} + fi +fi + +if env | grep "DOCKER_VERNEMQ_DISCOVERY_NODE" -q; then + discovery_node=$DOCKER_VERNEMQ_DISCOVERY_NODE + if [ -n "$DOCKER_VERNEMQ_SWARM" ]; then + tmp='' + while [[ -z "$tmp" ]]; do + tmp=$(getent hosts tasks.$discovery_node | awk '{print $1}' | head -n 1) + sleep 1 + done + discovery_node=$tmp + fi + if [ -n "$DOCKER_VERNEMQ_COMPOSE" ]; then + tmp='' + while [[ -z "$tmp" ]]; do + tmp=$(getent hosts $discovery_node | awk '{print $1}' | head -n 1) + sleep 1 + done + discovery_node=$tmp + fi + + sed -i.bak -r "/-eval.+/d" ${VERNEMQ_VM_ARGS_FILE} + echo "-eval \"vmq_server_cmd:node_join('VerneMQ@$discovery_node')\"" >> ${VERNEMQ_VM_ARGS_FILE} +fi + +# If you encounter "SSL certification error (subject name does not match the host name)", you may try to set DOCKER_VERNEMQ_KUBERNETES_INSECURE to "1". +insecure="" +if env | grep "DOCKER_VERNEMQ_KUBERNETES_INSECURE" -q; then + echo "Using curl with \"--insecure\" argument to access kubernetes API without matching SSL certificate" + insecure="--insecure" +fi + +if env | grep "DOCKER_VERNEMQ_KUBERNETES_ISTIO_ENABLED" -q; then + istio_health + while [ $status != 0 ]; do + istio_health + sleep 1 + done + echo "Istio ready" +fi + +# Function to call a HTTP GET request on the given URL Path, using the hostname +# of the current k8s cluster name. Usage: "k8sCurlGet /my/path" +function k8sCurlGet () { + local urlPath=$1 + + local hostname="kubernetes.default.svc.${DOCKER_VERNEMQ_KUBERNETES_CLUSTER_NAME}" + local certsFile="${SECRETS_KUBERNETES_DIR}/ca.crt" + local token=$(cat ${SECRETS_KUBERNETES_DIR}/token) + local header="Authorization: Bearer ${token}" + local url="https://${hostname}/${urlPath}" + + curl -sS ${insecure} --cacert ${certsFile} -H "${header}" ${url} \ + || ( echo "### Error on accessing URL ${url}" ) +} + +DOCKER_VERNEMQ_KUBERNETES_CLUSTER_NAME=${DOCKER_VERNEMQ_KUBERNETES_CLUSTER_NAME:-cluster.local} +if [ -d "${SECRETS_KUBERNETES_DIR}" ] ; then + # Let's get the namespace if it isn't set + DOCKER_VERNEMQ_KUBERNETES_NAMESPACE=${DOCKER_VERNEMQ_KUBERNETES_NAMESPACE:-$(cat "${SECRETS_KUBERNETES_DIR}/namespace")} + + # Check the API access that will be needed in the TERM signal handler + podResponse=$(k8sCurlGet api/v1/namespaces/${DOCKER_VERNEMQ_KUBERNETES_NAMESPACE}/pods/$(hostname) ) + statefulSetName=$(echo ${podResponse} | jq -r '.metadata.ownerReferences[0].name') + statefulSetPath="apis/apps/v1/namespaces/${DOCKER_VERNEMQ_KUBERNETES_NAMESPACE}/statefulsets/${statefulSetName}" + statefulSetResponse=$(k8sCurlGet ${statefulSetPath} ) + isCodeForbidden=$(echo ${statefulSetResponse} | jq '.code == 403') + if [[ ${isCodeForbidden} == "true" ]]; then + echo "Permission error: Cannot access URL ${statefulSetPath}: $(echo ${statefulSetResponse} | jq '.reason,.code,.message')" + exit 1 + else + numReplicas=$(echo ${statefulSetResponse} | jq '.status.replicas') + echo "Permissions ok: Our pod $(hostname) belongs to StatefulSet ${statefulSetName} with ${numReplicas} replicas" + fi +fi + +# Set up kubernetes node discovery +start_join_cluster=0 +if env | grep "DOCKER_VERNEMQ_DISCOVERY_KUBERNETES" -q; then + # Let's set our nodename correctly + # https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#list-pod-v1-core + podList=$(k8sCurlGet "api/v1/namespaces/${DOCKER_VERNEMQ_KUBERNETES_NAMESPACE}/pods?labelSelector=${DOCKER_VERNEMQ_KUBERNETES_LABEL_SELECTOR}") + VERNEMQ_KUBERNETES_SUBDOMAIN=${DOCKER_VERNEMQ_KUBERNETES_SUBDOMAIN:-$(echo ${podList} | jq '.items[0].spec.subdomain' | tr '\n' '"' | sed 's/"//g')} + if [[ $VERNEMQ_KUBERNETES_SUBDOMAIN == "null" ]]; then + VERNEMQ_KUBERNETES_HOSTNAME=${MY_POD_NAME}.${DOCKER_VERNEMQ_KUBERNETES_NAMESPACE}.svc.${DOCKER_VERNEMQ_KUBERNETES_CLUSTER_NAME} + else + VERNEMQ_KUBERNETES_HOSTNAME=${MY_POD_NAME}.${VERNEMQ_KUBERNETES_SUBDOMAIN}.${DOCKER_VERNEMQ_KUBERNETES_NAMESPACE}.svc.${DOCKER_VERNEMQ_KUBERNETES_CLUSTER_NAME} + fi + + sed -i.bak -r "s/VerneMQ@.+/VerneMQ@${VERNEMQ_KUBERNETES_HOSTNAME}/" ${VERNEMQ_VM_ARGS_FILE} + # Hack into K8S DNS resolution (temporarily) + kube_pod_names=$(echo ${podList} | jq '.items[].spec.hostname' | sed 's/"//g' | tr '\n' ' ' | sed 's/ *$//') + + for kube_pod_name in $kube_pod_names; do + if [[ $kube_pod_name == "null" ]]; then + echo "Kubernetes discovery selected, but no pods found. Maybe we're the first?" + echo "Anyway, we won't attempt to join any cluster." + break + fi + if [[ $kube_pod_name != $MY_POD_NAME ]]; then + discoveryHostname="${kube_pod_name}.${VERNEMQ_KUBERNETES_SUBDOMAIN}.${DOCKER_VERNEMQ_KUBERNETES_NAMESPACE}.svc.${DOCKER_VERNEMQ_KUBERNETES_CLUSTER_NAME}" + start_join_cluster=1 + echo "Will join an existing Kubernetes cluster with discovery node at ${discoveryHostname}" + echo "-eval \"vmq_server_cmd:node_join('VerneMQ@${discoveryHostname}')\"" >> ${VERNEMQ_VM_ARGS_FILE} + echo "Did I previously leave the cluster? If so, purging old state." + curl -fsSL http://${discoveryHostname}:8888/status.json >/dev/null 2>&1 || + (echo "Can't download status.json, better to exit now" && exit 1) + curl -fsSL http://${discoveryHostname}:8888/status.json | grep -q ${VERNEMQ_KUBERNETES_HOSTNAME} || + (echo "Cluster doesn't know about me, this means I've left previously. Purging old state..." && rm -rf /vernemq/data/*) + break + fi + done +fi + +if [ -f "${VERNEMQ_CONF_LOCAL_FILE}" ]; then + cp "${VERNEMQ_CONF_LOCAL_FILE}" ${VERNEMQ_CONF_FILE} + sed -i -r "s/###IPADDRESS###/${IP_ADDRESS}/" ${VERNEMQ_CONF_FILE} +else + sed -i '/########## Start ##########/,/########## End ##########/d' ${VERNEMQ_CONF_FILE} + + echo "########## Start ##########" >> ${VERNEMQ_CONF_FILE} + + env | grep DOCKER_VERNEMQ | grep -v 'DISCOVERY_NODE\|KUBERNETES\|SWARM\|COMPOSE\|DOCKER_VERNEMQ_USER' | cut -c 16- | awk '{match($0,/^[A-Z0-9_]*/)}{print tolower(substr($0,RSTART,RLENGTH)) substr($0,RLENGTH+1)}' | sed 's/__/./g' >> ${VERNEMQ_CONF_FILE} + + users_are_set=$(env | grep DOCKER_VERNEMQ_USER) + if [ ! -z "$users_are_set" ]; then + echo "vmq_passwd.password_file = /vernemq/etc/vmq.passwd" >> ${VERNEMQ_CONF_FILE} + touch /vernemq/etc/vmq.passwd + fi + + for vernemq_user in $(env | grep DOCKER_VERNEMQ_USER); do + username=$(echo $vernemq_user | awk -F '=' '{ print $1 }' | sed 's/DOCKER_VERNEMQ_USER_//g' | tr '[:upper:]' '[:lower:]') + password=$(echo $vernemq_user | awk -F '=' '{ print $2 }') + /vernemq/bin/vmq-passwd /vernemq/etc/vmq.passwd $username <> ${VERNEMQ_CONF_FILE} + fi + + if [ -z "$DOCKER_VERNEMQ_ERLANG__DISTRIBUTION__PORT_RANGE__MAXIMUM" ]; then + echo "erlang.distribution.port_range.maximum = 9109" >> ${VERNEMQ_CONF_FILE} + fi + + if [ -z "$DOCKER_VERNEMQ_LISTENER__TCP__DEFAULT" ]; then + echo "listener.tcp.default = ${IP_ADDRESS}:1883" >> ${VERNEMQ_CONF_FILE} + fi + + if [ -z "$DOCKER_VERNEMQ_LISTENER__WS__DEFAULT" ]; then + echo "listener.ws.default = ${IP_ADDRESS}:8080" >> ${VERNEMQ_CONF_FILE} + fi + + if [ -z "$DOCKER_VERNEMQ_LISTENER__VMQ__CLUSTERING" ]; then + echo "listener.vmq.clustering = ${IP_ADDRESS}:44053" >> ${VERNEMQ_CONF_FILE} + fi + + if [ -z "$DOCKER_VERNEMQ_LISTENER__HTTP__METRICS" ]; then + echo "listener.http.metrics = ${IP_ADDRESS}:8888" >> ${VERNEMQ_CONF_FILE} + fi + + echo "########## End ##########" >> ${VERNEMQ_CONF_FILE} +fi + +if [ ! -z "$DOCKER_VERNEMQ_ERLANG__MAX_PORTS" ]; then + sed -i.bak -r "s/\+Q.+/\+Q ${DOCKER_VERNEMQ_ERLANG__MAX_PORTS}/" ${VERNEMQ_VM_ARGS_FILE} +fi + +if [ ! -z "$DOCKER_VERNEMQ_ERLANG__PROCESS_LIMIT" ]; then + sed -i.bak -r "s/\+P.+/\+P ${DOCKER_VERNEMQ_ERLANG__PROCESS_LIMIT}/" ${VERNEMQ_VM_ARGS_FILE} +fi + +if [ ! -z "$DOCKER_VERNEMQ_ERLANG__MAX_ETS_TABLES" ]; then + sed -i.bak -r "s/\+e.+/\+e ${DOCKER_VERNEMQ_ERLANG__MAX_ETS_TABLES}/" ${VERNEMQ_VM_ARGS_FILE} +fi + +if [ ! -z "$DOCKER_VERNEMQ_ERLANG__DISTRIBUTION_BUFFER_SIZE" ]; then + sed -i.bak -r "s/\+zdbbl.+/\+zdbbl ${DOCKER_VERNEMQ_ERLANG__DISTRIBUTION_BUFFER_SIZE}/" ${VERNEMQ_VM_ARGS_FILE} +fi + +# Check configuration file +/vernemq/bin/vernemq config generate 2>&1 > /dev/null | tee /tmp/config.out | grep error + +if [ $? -ne 1 ]; then + echo "configuration error, exit" + echo "$(cat /tmp/config.out)" + exit $? +fi + +pid=0 + +# SIGUSR1-handler +siguser1_handler() { + echo "stopped" +} + +# SIGTERM-handler +sigterm_handler() { + if [ $pid -ne 0 ]; then + if [ -d "${SECRETS_KUBERNETES_DIR}" ] ; then + # this will stop the VerneMQ process, but first drain the node from all existing client sessions (-k) + if [ -n "$VERNEMQ_KUBERNETES_HOSTNAME" ]; then + terminating_node_name=VerneMQ@$VERNEMQ_KUBERNETES_HOSTNAME + else + terminating_node_name=VerneMQ@$IP_ADDRESS + fi + podList=$(k8sCurlGet "api/v1/namespaces/${DOCKER_VERNEMQ_KUBERNETES_NAMESPACE}/pods?labelSelector=${DOCKER_VERNEMQ_KUBERNETES_LABEL_SELECTOR}") + kube_pod_names=$(echo ${podList} | jq '.items[].spec.hostname' | sed 's/"//g' | tr '\n' ' ' | sed 's/ *$//') + if [ "$kube_pod_names" = "$MY_POD_NAME" ]; then + echo "I'm the only pod remaining. Not performing leave and/or state purge." + /vernemq/bin/vmq-admin node stop >/dev/null + else + # https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#read-pod-v1-core + podResponse=$(k8sCurlGet api/v1/namespaces/${DOCKER_VERNEMQ_KUBERNETES_NAMESPACE}/pods/$(hostname) ) + statefulSetName=$(echo ${podResponse} | jq -r '.metadata.ownerReferences[0].name') + + # https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#-strong-read-operations-statefulset-v1-apps-strong- + statefulSetResponse=$(k8sCurlGet "apis/apps/v1/namespaces/${DOCKER_VERNEMQ_KUBERNETES_NAMESPACE}/statefulsets/${statefulSetName}" ) + + isCodeForbidden=$(echo ${statefulSetResponse} | jq '.code == 403') + if [[ ${isCodeForbidden} == "true" ]]; then + echo "Permission error: Cannot access URL ${statefulSetPath}: $(echo ${statefulSetResponse} | jq '.reason,.code,.message')" + fi + + reschedule=$(echo ${statefulSetResponse} | jq '.status.replicas == .status.readyReplicas') + scaled_down=$(echo ${statefulSetResponse} | jq '.status.currentReplicas == .status.updatedReplicas') + + if [[ $reschedule == "true" ]]; then + # Perhaps is an scale down? + if [[ $scaled_down == "true" ]]; then + echo "Seems that this is a scale down scenario. Leaving cluster." + /vernemq/bin/vmq-admin cluster leave node=${terminating_node_name} -k && rm -rf /vernemq/data/* + else + echo "Reschedule is true. Not leaving the cluster." + /vernemq/bin/vmq-admin node stop >/dev/null + fi + else + echo "Reschedule is false. Leaving the cluster." + /vernemq/bin/vmq-admin cluster leave node=${terminating_node_name} -k && rm -rf /vernemq/data/* + fi + fi + else + if [ -n "$DOCKER_VERNEMQ_SWARM" ]; then + terminating_node_name=VerneMQ@$(hostname -i) + # For Swarm we keep the old "cluster leave" approach for now + echo "Swarm node is leaving the cluster." + /vernemq/bin/vmq-admin cluster leave node=${terminating_node_name} -k && rm -rf /vernemq/data/* + else + # In non-k8s mode: Stop the vernemq node gracefully + /vernemq/bin/vmq-admin node stop >/dev/null + fi + fi + kill -s TERM ${pid} + WAITFOR_PID=${pid} + pid=0 + wait ${WAITFOR_PID} + fi + exit 143; # 128 + 15 -- SIGTERM +} + +if [ ! -s ${VERNEMQ_VM_ARGS_FILE} ]; then + echo "ls -l ${VERNEMQ_ETC_DIR}" + ls -l ${VERNEMQ_ETC_DIR} + echo "###" >&2 + echo "### Configuration file ${VERNEMQ_VM_ARGS_FILE} is empty! This will not work." >&2 + echo "### Exiting now." >&2 + echo "###" >&2 + exit 1 +fi + +# Setup OS signal handlers +trap 'siguser1_handler' SIGUSR1 +trap 'sigterm_handler' SIGTERM + +# Start VerneMQ +/vernemq/bin/vernemq console -noshell -noinput $@ & +pid=$! +if [ $start_join_cluster -eq 1 ]; then + mkdir -p /var/log/vernemq/log + join_cluster > /var/log/vernemq/log/join_cluster.log & +fi +if [ -n "$API_KEY" ]; then + sleep 10 && echo "Adding API_KEY..." && /vernemq/bin/vmq-admin api-key add key="${API_KEY:-DEFAULT}" + vmq-admin api-key show +fi +wait $pid diff --git a/docker/vernemq/files/vm.args b/docker/vernemq/files/vm.args new file mode 100644 index 0000000000..afb3c022bb --- /dev/null +++ b/docker/vernemq/files/vm.args @@ -0,0 +1,15 @@ ++P 512000 ++e 256000 +-env ERL_CRASH_DUMP /erl_crash.dump +-env ERL_FULLSWEEP_AFTER 0 ++Q 512000 ++A 64 +-setcookie vmq +-name VerneMQ@127.0.0.1 ++K true ++W w ++sbwt none ++sbwtdcpu none ++sbwtdio none +-smp enable ++zdbbl 32768 diff --git a/go.mod b/go.mod index c9f688f67c..57bf5c39fb 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.23.4 require ( github.com/0x6flab/namegenerator v1.4.0 github.com/absmach/callhome v0.14.0 - github.com/absmach/certs v0.0.0-20250127084046-fb0da0712b2b + github.com/absmach/certs v0.0.0-20241014135535-3f118b801054 github.com/absmach/mgate v0.4.5 github.com/absmach/senml v1.0.6 github.com/authzed/authzed-go v1.3.0 @@ -13,6 +13,7 @@ require ( github.com/authzed/spicedb v1.40.0 github.com/caarlos0/env/v11 v11.3.1 github.com/cenkalti/backoff/v4 v4.3.0 + github.com/eclipse/paho.mqtt.golang v1.5.0 github.com/fatih/color v1.18.0 github.com/go-chi/chi/v5 v5.2.1 github.com/go-kit/kit v0.13.0 @@ -64,7 +65,7 @@ require ( github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect - github.com/antlr4-go/antlr/v4 v4.13.1 // indirect + github.com/antlr4-go/antlr/v4 v4.13.0 // indirect github.com/authzed/cel-go v0.20.2 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/ccoveille/go-safecast v1.5.0 // indirect @@ -79,7 +80,6 @@ require ( github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/dsnet/golib/memfile v1.0.0 // indirect - github.com/eclipse/paho.mqtt.golang v1.5.0 // indirect github.com/emirpasic/gods v1.18.1 // indirect github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect @@ -91,7 +91,7 @@ require ( github.com/go-logfmt/logfmt v0.6.0 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/goccy/go-json v0.10.5 // indirect + github.com/goccy/go-json v0.10.3 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/gopherjs/gopherjs v1.17.2 // indirect @@ -102,9 +102,9 @@ require ( github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-retryablehttp v0.7.7 // indirect github.com/hashicorp/go-rootcerts v1.0.2 // indirect - github.com/hashicorp/go-secure-stdlib/parseutil v0.1.9 // indirect + github.com/hashicorp/go-secure-stdlib/parseutil v0.1.8 // indirect github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect - github.com/hashicorp/go-sockaddr v1.0.7 // indirect + github.com/hashicorp/go-sockaddr v1.0.6 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jackc/pgio v1.0.0 // indirect @@ -114,13 +114,13 @@ require ( github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/jtolds/gls v4.20.0+incompatible // indirect github.com/jzelinskie/stringz v0.0.3 // indirect - github.com/klauspost/compress v1.17.11 // indirect + github.com/klauspost/compress v1.17.9 // indirect github.com/lestrrat-go/blackmagic v1.0.2 // indirect github.com/lestrrat-go/httpcc v1.0.1 // indirect github.com/lestrrat-go/httprc v1.0.6 // indirect github.com/lestrrat-go/iter v1.0.2 // indirect github.com/lestrrat-go/option v1.0.1 // indirect - github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect @@ -131,18 +131,18 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // indirect github.com/opencontainers/runc v1.1.14 // indirect - github.com/pion/dtls/v3 v3.0.4 // indirect - github.com/pion/logging v0.2.3 // indirect + github.com/pion/dtls/v3 v3.0.2 // indirect + github.com/pion/logging v0.2.2 // indirect github.com/pion/transport/v3 v3.0.7 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/planetscale/vtprotobuf v0.6.1-0.20240917153116-6f2963f01587 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_model v0.6.1 // indirect - github.com/prometheus/common v0.62.0 // indirect + github.com/prometheus/common v0.61.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect github.com/rs/zerolog v1.33.0 // indirect github.com/ryanuber/go-glob v1.0.0 // indirect - github.com/samber/lo v1.49.1 // indirect + github.com/samber/lo v1.49.0 // indirect github.com/segmentio/asm v1.2.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/smarty/assertions v1.15.0 // indirect @@ -157,7 +157,7 @@ require ( go.opentelemetry.io/otel/metric v1.34.0 // indirect go.opentelemetry.io/proto/otlp v1.5.0 // indirect go.uber.org/atomic v1.11.0 // indirect - golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c // indirect + golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect golang.org/x/net v0.34.0 // indirect golang.org/x/sys v0.30.0 // indirect golang.org/x/text v0.22.0 // indirect @@ -166,5 +166,5 @@ require ( gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/utils v0.0.0-20241210054802-24370beab758 // indirect + k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect ) diff --git a/go.sum b/go.sum index cd8c8ddca6..00582f075e 100644 --- a/go.sum +++ b/go.sum @@ -19,14 +19,14 @@ github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrd github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= github.com/absmach/callhome v0.14.0 h1:zB4tIZJ1YUmZ1VGHFPfMA/Lo6/Mv19y2dvoOiXj2BWs= github.com/absmach/callhome v0.14.0/go.mod h1:l12UJOfibK4Muvg/AbupHuquNV9qSz/ROdTEPg7f2Vk= -github.com/absmach/certs v0.0.0-20250127084046-fb0da0712b2b h1:EGIqL1bARjRSS7kH98Q5O/g7lZN/Q0KtAVX5mxRcq84= -github.com/absmach/certs v0.0.0-20250127084046-fb0da0712b2b/go.mod h1:g6Kqge7RVxwt+LRxqt+09cqa2SgPAwXvIPoyPsEqZlQ= +github.com/absmach/certs v0.0.0-20241014135535-3f118b801054 h1:NsIwp+ueKxDx8XftruA4hz8WUgyWq7eBE344nJt0LJg= +github.com/absmach/certs v0.0.0-20241014135535-3f118b801054/go.mod h1:bEAb/HjPztlrMmz8dLeJTke4Tzu9yW3+hY5eldEUtSY= github.com/absmach/mgate v0.4.5 h1:l6RmrEsR9jxkdb9WHUSecmT0HA41TkZZQVffFfUAIfI= github.com/absmach/mgate v0.4.5/go.mod h1:IvRIHZexZPEIAPmmaJF0L5DY2ERjj+GxRGitOW4s6qo= github.com/absmach/senml v1.0.6 h1:WPeIl6vQ00k7ghWSZYT/QP0KUxq2+4zQoaC7240pLFk= github.com/absmach/senml v1.0.6/go.mod h1:QnJNPy1DJPy0+qUW21PTcH/xoh0LgfYZxTfwriMIvmQ= -github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= -github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= +github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI= +github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g= github.com/authzed/authzed-go v1.3.0 h1:jKIMpYDy+6WoOwl32HRURxLZxNGm+I7ObUlTntEPcXA= github.com/authzed/authzed-go v1.3.0/go.mod h1:MYkXImtFAxrM/bVZvmC/WO+gZC9RLlvpCM51SLaUZb0= github.com/authzed/cel-go v0.20.2 h1:GlmLecGry7Z8HU0k+hmaHHUV05ZHrsFxduXHtIePvck= @@ -99,6 +99,8 @@ github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2 github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= +github.com/go-chi/chi v1.5.5 h1:vOB/HbEMt9QqBqErz07QehcOKHaWFtuj87tTDVz2qXE= +github.com/go-chi/chi v1.5.5/go.mod h1:C9JqLr3tIYjDOZpzn+BCuxY8z8vmca43EeMgyZt7irw= github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8= github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk= @@ -125,8 +127,8 @@ github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqw github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-test/deep v1.0.2 h1:onZX1rnHT3Wv6cqNgYyFOOlgVKJrksuCMCRvJStbMYw= github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= -github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= -github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= +github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gofrs/uuid/v5 v5.3.0 h1:m0mUMr+oVYUdxpMLgSYCZiXe7PuVPnI94+OMeVBNedk= @@ -171,12 +173,12 @@ github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISH github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= -github.com/hashicorp/go-secure-stdlib/parseutil v0.1.9 h1:FW0YttEnUNDJ2WL9XcrrfteS1xW8u+sh4ggM8pN5isQ= -github.com/hashicorp/go-secure-stdlib/parseutil v0.1.9/go.mod h1:Ll013mhdmsVDuoIXVfBtvgGJsXDYkTw1kooNcoCXuE0= +github.com/hashicorp/go-secure-stdlib/parseutil v0.1.8 h1:iBt4Ew4XEGLfh6/bPk4rSYmuZJGizr6/x/AEizP0CQc= +github.com/hashicorp/go-secure-stdlib/parseutil v0.1.8/go.mod h1:aiJI+PIApBRQG7FZTEBx5GiiX+HbOHilUdNxUZi4eV0= github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts= github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4= -github.com/hashicorp/go-sockaddr v1.0.7 h1:G+pTkSO01HpR5qCxg7lxfsFEZaG+C0VssTy/9dbT+Fw= -github.com/hashicorp/go-sockaddr v1.0.7/go.mod h1:FZQbEYa1pxkQ7WLpyXJ6cbjpT8q0YgQaK/JakXqGyWw= +github.com/hashicorp/go-sockaddr v1.0.6 h1:RSG8rKU28VTUTvEKghe5gIhIQpv8evvNpnDEyqO4u9I= +github.com/hashicorp/go-sockaddr v1.0.6/go.mod h1:uoUUmtwU7n9Dv3O4SNLeFvg0SxQ3lyjsj6+CCykpaxI= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/vault/api v1.15.0 h1:O24FYQCWwhwKnF7CuSqP30S51rTV7vz1iACXE/pj5DA= @@ -251,8 +253,8 @@ github.com/jzelinskie/stringz v0.0.3 h1:0GhG3lVMYrYtIvRbxvQI6zqRTT1P1xyQlpa0FhfU github.com/jzelinskie/stringz v0.0.3/go.mod h1:hHYbgxJuNLRw91CmpuFsYEOyQqpDVFg8pvEh23vy4P0= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= -github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= +github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -285,9 +287,8 @@ github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= -github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= @@ -327,10 +328,10 @@ github.com/ory/dockertest/v3 v3.11.0/go.mod h1:VIPxS1gwT9NpPOrfD3rACs8Y9Z7yhzO4S github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/pion/dtls/v3 v3.0.4 h1:44CZekewMzfrn9pmGrj5BNnTMDCFwr+6sLH+cCuLM7U= -github.com/pion/dtls/v3 v3.0.4/go.mod h1:R373CsjxWqNPf6MEkfdy3aSe9niZvL/JaKlGeFphtMg= -github.com/pion/logging v0.2.3 h1:gHuf0zpoh1GW67Nr6Gj4cv5Z9ZscU7g/EaoC/Ke/igI= -github.com/pion/logging v0.2.3/go.mod h1:z8YfknkquMe1csOrxK5kc+5/ZPAzMxbKLX5aXpbpC90= +github.com/pion/dtls/v3 v3.0.2 h1:425DEeJ/jfuTTghhUDW0GtYZYIwwMtnKKJNMcWccTX0= +github.com/pion/dtls/v3 v3.0.2/go.mod h1:dfIXcFkKoujDQ+jtd8M6RgqKK3DuaUilm3YatAbGp5k= +github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY= +github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms= github.com/pion/transport/v3 v3.0.7 h1:iRbMH05BzSNwhILHoBoAPxoB9xQgOaJk+591KC9P1o0= github.com/pion/transport/v3 v3.0.7/go.mod h1:YleKiTZ4vqNxVwh77Z0zytYi7rXHl7j6uPLGhhz9rwo= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -350,8 +351,8 @@ github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/j github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= -github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= -github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= +github.com/prometheus/common v0.61.0 h1:3gv/GThfX0cV2lpO7gkTUwZru38mxevy90Bj8YFSRQQ= +github.com/prometheus/common v0.61.0/go.mod h1:zr29OCN/2BsJRaFwG8QOBr41D6kkchKbpeNH7pAjb/s= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/rabbitmq/amqp091-go v1.10.0 h1:STpn5XsHlHGcecLmMFCtg7mqq0RnD+zFr4uzukfVhBw= @@ -372,8 +373,8 @@ github.com/rubenv/sql-migrate v1.7.1/go.mod h1:Ob2Psprc0/3ggbM6wCzyYVFFuc6FyZrb2 github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= -github.com/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew= -github.com/samber/lo v1.49.1/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o= +github.com/samber/lo v1.49.0 h1:AGnTnQrg1jpFuwECPUSoxZCfVH5W22b605kWSry3YxM= +github.com/samber/lo v1.49.0/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= @@ -485,8 +486,8 @@ golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZP golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c h1:KL/ZBHXgKGVmuZBZ01Lt57yE5ws8ZPSkkihmEyq7FXc= -golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU= +golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk= +golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= @@ -635,7 +636,7 @@ gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -k8s.io/utils v0.0.0-20241210054802-24370beab758 h1:sdbE21q2nlQtFh65saZY+rRM6x6aJJI8IUa1AmH/qa0= -k8s.io/utils v0.0.0-20241210054802-24370beab758/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A= +k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= moul.io/http2curl v1.0.0 h1:6XwpyZOYsgZJrU8exnG87ncVkU1FVCcTRpwzOkTDUi8= moul.io/http2curl v1.0.0/go.mod h1:f6cULg+e4Md/oW1cYmwW4IWQOVl2lGbmCNGOHvzX2kE= diff --git a/mqtt/README.md b/mqtt/README.md index 467cf090b5..aaa92c1307 100644 --- a/mqtt/README.md +++ b/mqtt/README.md @@ -6,19 +6,22 @@ MQTT adapter provides an MQTT API for sending messages through the platform. MQT The service is configured using the environment variables presented in the following table. Note that any unset variables will be replaced with their default values. -| Variable | Description | Default | -| ----------------------------------------- | ----------------------------------------------------------------------------------- | --------------------------------- | +| Variable | Description | Default | +| ---------------------------------------- | ----------------------------------------------------------------------------------- | --------------------------------- | | SMQ_MQTT_ADAPTER_LOG_LEVEL | Log level for the MQTT Adapter (debug, info, warn, error) | info | | SMQ_MQTT_ADAPTER_MQTT_PORT | mProxy port | 1883 | | SMQ_MQTT_ADAPTER_MQTT_TARGET_HOST | MQTT broker host | localhost | | SMQ_MQTT_ADAPTER_MQTT_TARGET_PORT | MQTT broker port | 1883 | +| SMQ_MQTT_ADAPTER_MQTT_QOS | MQTT broker QoS | 1 | +| SMQ_MQTT_ADAPTER_FORWARDER_TIMEOUT | MQTT forwarder for multiprotocol communication timeout | 30s | | SMQ_MQTT_ADAPTER_MQTT_TARGET_HEALTH_CHECK | URL of broker health check | "" | | SMQ_MQTT_ADAPTER_WS_PORT | mProxy MQTT over WS port | 8080 | | SMQ_MQTT_ADAPTER_WS_TARGET_HOST | MQTT broker host for MQTT over WS | localhost | | SMQ_MQTT_ADAPTER_WS_TARGET_PORT | MQTT broker port for MQTT over WS | 8080 | +| SMQ_MQTT_ADAPTER_WS_TARGET_PATH | MQTT broker MQTT over WS path | /mqtt | | SMQ_MQTT_ADAPTER_INSTANCE | Instance name for MQTT adapter | "" | -| SMQ_CLIENTS_AUTH_GRPC_URL | Clients service Auth gRPC URL | | -| SMQ_CLIENTS_AUTH_GRPC_TIMEOUT | Clients service Auth gRPC request timeout in seconds | 1s | +| SMQ_CLIENTS_AUTH_GRPC_URL | Clients service Auth gRPC URL | | +| SMQ_CLIENTS_AUTH_GRPC_TIMEOUT | Clients service Auth gRPC request timeout in seconds | 1s | | SMQ_CLIENTS_AUTH_GRPC_CLIENT_CERT | Path to the PEM encoded clients service Auth gRPC client certificate file | "" | | SMQ_CLIENTS_AUTH_GRPC_CLIENT_KEY | Path to the PEM encoded clients service Auth gRPC client key file | "" | | SMQ_CLIENTS_AUTH_GRPC_SERVER_CERTS | Path to the PEM encoded clients server Auth gRPC server trusted CA certificate file | "" | @@ -26,7 +29,7 @@ The service is configured using the environment variables presented in the follo | SMQ_MESSAGE_BROKER_URL | Message broker instance URL | | | SMQ_JAEGER_URL | Jaeger server URL | | | SMQ_JAEGER_TRACE_RATIO | Jaeger sampling ratio | 1.0 | -| SMQ_SEND_TELEMETRY | Send telemetry to supermq call home server | true | +| SMQ_SEND_TELEMETRY | Send telemetry to supermq call home server | true | | SMQ_MQTT_ADAPTER_INSTANCE_ID | Service instance ID | "" | ## Deployment @@ -53,10 +56,13 @@ SMQ_MQTT_ADAPTER_LOG_LEVEL=info \ SMQ_MQTT_ADAPTER_MQTT_PORT=1883 \ SMQ_MQTT_ADAPTER_MQTT_TARGET_HOST=localhost \ SMQ_MQTT_ADAPTER_MQTT_TARGET_PORT=1883 \ +SMQ_MQTT_ADAPTER_MQTT_QOS=1 \ +SMQ_MQTT_ADAPTER_FORWARDER_TIMEOUT=30s \ SMQ_MQTT_ADAPTER_MQTT_TARGET_HEALTH_CHECK="" \ SMQ_MQTT_ADAPTER_WS_PORT=8080 \ SMQ_MQTT_ADAPTER_WS_TARGET_HOST=localhost \ SMQ_MQTT_ADAPTER_WS_TARGET_PORT=8080 \ +SMQ_MQTT_ADAPTER_WS_TARGET_PATH=/mqtt \ SMQ_MQTT_ADAPTER_INSTANCE="" \ SMQ_CLIENTS_AUTH_GRPC_URL=localhost:7000 \ SMQ_CLIENTS_AUTH_GRPC_TIMEOUT=1s \ diff --git a/mqtt/forwarder.go b/mqtt/forwarder.go new file mode 100644 index 0000000000..323854809c --- /dev/null +++ b/mqtt/forwarder.go @@ -0,0 +1,75 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package mqtt + +import ( + "context" + "fmt" + "log/slog" + "strings" + + "github.com/absmach/supermq/pkg/messaging" +) + +// Forwarder specifies MQTT forwarder interface API. +type Forwarder interface { + // Forward subscribes to the Subscriber and + // publishes messages using provided Publisher. + Forward(ctx context.Context, id string, sub messaging.Subscriber, pub messaging.Publisher) error +} + +type forwarder struct { + topic string + logger *slog.Logger +} + +// NewForwarder returns new Forwarder implementation. +func NewForwarder(topic string, logger *slog.Logger) Forwarder { + return forwarder{ + topic: topic, + logger: logger, + } +} + +func (f forwarder) Forward(ctx context.Context, id string, sub messaging.Subscriber, pub messaging.Publisher) error { + subCfg := messaging.SubscriberConfig{ + ID: id, + Topic: f.topic, + Handler: handle(ctx, pub, f.logger), + } + + return sub.Subscribe(ctx, subCfg) +} + +func handle(ctx context.Context, pub messaging.Publisher, logger *slog.Logger) handleFunc { + return func(msg *messaging.Message) error { + if msg.GetProtocol() == protocol { + return nil + } + // Use concatenation instead of fmt.Sprintf for the + // sake of simplicity and performance. + topic := "channels/" + msg.GetChannel() + "/messages" + if msg.GetSubtopic() != "" { + topic = topic + "/" + strings.ReplaceAll(msg.GetSubtopic(), ".", "/") + } + + go func() { + if err := pub.Publish(ctx, topic, msg); err != nil { + logger.Warn(fmt.Sprintf("Failed to forward message: %s", err)) + } + }() + + return nil + } +} + +type handleFunc func(msg *messaging.Message) error + +func (h handleFunc) Handle(msg *messaging.Message) error { + return h(msg) +} + +func (h handleFunc) Cancel() error { + return nil +} diff --git a/mqtt/tracing/doc.go b/mqtt/tracing/doc.go new file mode 100644 index 0000000000..557d3934f1 --- /dev/null +++ b/mqtt/tracing/doc.go @@ -0,0 +1,12 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +// Package tracing provides tracing instrumentation for SuperMQ MQTT adapter service. +// +// This package provides tracing middleware for SuperMQ MQTT adapter service. +// It can be used to trace incoming requests and add tracing capabilities to +// SuperMQ MQTT adapter service. +// +// For more details about tracing instrumentation for SuperMQ messaging refer +// to the documentation at https://docs.supermq.abstractmachines.fr/tracing/. +package tracing diff --git a/mqtt/tracing/forwarder.go b/mqtt/tracing/forwarder.go new file mode 100644 index 0000000000..c4db29ecbf --- /dev/null +++ b/mqtt/tracing/forwarder.go @@ -0,0 +1,63 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package tracing + +import ( + "context" + "fmt" + + "github.com/absmach/supermq/mqtt" + "github.com/absmach/supermq/pkg/messaging" + "github.com/absmach/supermq/pkg/server" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" +) + +const forwardOP = "process" + +var _ mqtt.Forwarder = (*forwarderMiddleware)(nil) + +type forwarderMiddleware struct { + topic string + forwarder mqtt.Forwarder + tracer trace.Tracer + host server.Config +} + +// New creates new mqtt forwarder tracing middleware. +func New(config server.Config, tracer trace.Tracer, forwarder mqtt.Forwarder, topic string) mqtt.Forwarder { + return &forwarderMiddleware{ + forwarder: forwarder, + tracer: tracer, + topic: topic, + host: config, + } +} + +// Forward traces mqtt forward operations. +func (fm *forwarderMiddleware) Forward(ctx context.Context, id string, sub messaging.Subscriber, pub messaging.Publisher) error { + subject := fmt.Sprintf("channels.%s.messages", fm.topic) + spanName := fmt.Sprintf("%s %s", subject, forwardOP) + + ctx, span := fm.tracer.Start(ctx, + spanName, + trace.WithAttributes( + attribute.String("messaging.system", "mqtt"), + attribute.Bool("messaging.destination.anonymous", false), + attribute.String("messaging.destination.template", "channels/{channelID}/messages/*"), + attribute.Bool("messaging.destination.temporary", true), + attribute.String("network.protocol.name", "mqtt"), + attribute.String("network.protocol.version", "3.1.1"), + attribute.String("network.transport", "tcp"), + attribute.String("network.type", "ipv4"), + attribute.String("messaging.operation", forwardOP), + attribute.String("messaging.client_id", id), + attribute.String("server.address", fm.host.Host), + attribute.String("server.socket.port", fm.host.Port), + ), + ) + defer span.End() + + return fm.forwarder.Forward(ctx, id, sub, pub) +} diff --git a/pkg/messaging/mqtt/docs.go b/pkg/messaging/mqtt/docs.go new file mode 100644 index 0000000000..2afbf14587 --- /dev/null +++ b/pkg/messaging/mqtt/docs.go @@ -0,0 +1,11 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +// Package mqtt hold the implementation of the Publisher and PubSub +// interfaces for the MQTT messaging system, the internal messaging +// broker of the SuperMQ IoT platform. Due to the practical requirements +// implementation Publisher is created alongside PubSub. The reason for +// this is that Subscriber implementation of MQTT brings the burden of +// additional struct fields which are not used by Publisher. Subscriber +// is not implemented separately because PubSub can be used where Subscriber is needed. +package mqtt diff --git a/pkg/messaging/mqtt/publisher.go b/pkg/messaging/mqtt/publisher.go new file mode 100644 index 0000000000..54364c31de --- /dev/null +++ b/pkg/messaging/mqtt/publisher.go @@ -0,0 +1,61 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package mqtt + +import ( + "context" + "errors" + "time" + + "github.com/absmach/supermq/pkg/messaging" + mqtt "github.com/eclipse/paho.mqtt.golang" +) + +var errPublishTimeout = errors.New("failed to publish due to timeout reached") + +var _ messaging.Publisher = (*publisher)(nil) + +type publisher struct { + client mqtt.Client + timeout time.Duration + qos uint8 +} + +// NewPublisher returns a new MQTT message publisher. +func NewPublisher(address string, qos uint8, timeout time.Duration) (messaging.Publisher, error) { + client, err := newClient(address, "mqtt-publisher", timeout) + if err != nil { + return nil, err + } + + ret := publisher{ + client: client, + timeout: timeout, + qos: qos, + } + return ret, nil +} + +func (pub publisher) Publish(ctx context.Context, topic string, msg *messaging.Message) error { + if topic == "" { + return ErrEmptyTopic + } + + // Publish only the payload and not the whole message. + token := pub.client.Publish(topic, byte(pub.qos), false, msg.GetPayload()) + if token.Error() != nil { + return token.Error() + } + + if ok := token.WaitTimeout(pub.timeout); !ok { + return errPublishTimeout + } + + return nil +} + +func (pub publisher) Close() error { + pub.client.Disconnect(uint(pub.timeout)) + return nil +} diff --git a/pkg/messaging/mqtt/pubsub.go b/pkg/messaging/mqtt/pubsub.go new file mode 100644 index 0000000000..37c81a122d --- /dev/null +++ b/pkg/messaging/mqtt/pubsub.go @@ -0,0 +1,230 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package mqtt + +import ( + "context" + "errors" + "fmt" + "log/slog" + "sync" + "time" + + "github.com/absmach/supermq/pkg/messaging" + mqtt "github.com/eclipse/paho.mqtt.golang" + "google.golang.org/protobuf/proto" +) + +const username = "supermq-mqtt" + +var ( + // ErrConnect indicates that connection to MQTT broker failed. + ErrConnect = errors.New("failed to connect to MQTT broker") + + // errSubscribeTimeout indicates that the subscription failed due to timeout. + errSubscribeTimeout = errors.New("failed to subscribe due to timeout reached") + + // errUnsubscribeTimeout indicates that unsubscribe failed due to timeout. + errUnsubscribeTimeout = errors.New("failed to unsubscribe due to timeout reached") + + // errUnsubscribeDeleteTopic indicates that unsubscribe failed because the topic was deleted. + errUnsubscribeDeleteTopic = errors.New("failed to unsubscribe due to deletion of topic") + + // ErrNotSubscribed indicates that the topic is not subscribed to. + ErrNotSubscribed = errors.New("not subscribed") + + // ErrEmptyTopic indicates the absence of topic. + ErrEmptyTopic = errors.New("empty topic") + + // ErrEmptyID indicates the absence of ID. + ErrEmptyID = errors.New("empty ID") +) + +var _ messaging.PubSub = (*pubsub)(nil) + +type subscription struct { + client mqtt.Client + topics []string + cancel func() error +} + +type pubsub struct { + publisher + logger *slog.Logger + mu sync.RWMutex + address string + timeout time.Duration + subscriptions map[string]subscription +} + +// NewPubSub returns MQTT message publisher/subscriber. +func NewPubSub(url string, qos uint8, timeout time.Duration, logger *slog.Logger) (messaging.PubSub, error) { + client, err := newClient(url, "mqtt-publisher", timeout) + if err != nil { + return nil, err + } + ret := &pubsub{ + publisher: publisher{ + client: client, + timeout: timeout, + qos: qos, + }, + address: url, + timeout: timeout, + logger: logger, + subscriptions: make(map[string]subscription), + } + return ret, nil +} + +func (ps *pubsub) Subscribe(ctx context.Context, cfg messaging.SubscriberConfig) error { + if cfg.ID == "" { + return ErrEmptyID + } + if cfg.Topic == "" { + return ErrEmptyTopic + } + ps.mu.Lock() + defer ps.mu.Unlock() + + s, ok := ps.subscriptions[cfg.ID] + // If the client exists, check if it's subscribed to the topic and unsubscribe if needed. + switch ok { + case true: + if ok := s.contains(cfg.Topic); ok { + if err := s.unsubscribe(cfg.Topic, ps.timeout); err != nil { + return err + } + } + default: + client, err := newClient(ps.address, cfg.ID, ps.timeout) + if err != nil { + return err + } + s = subscription{ + client: client, + topics: []string{}, + cancel: cfg.Handler.Cancel, + } + } + s.topics = append(s.topics, cfg.Topic) + ps.subscriptions[cfg.ID] = s + + token := s.client.Subscribe(cfg.Topic, byte(ps.qos), ps.mqttHandler(cfg.Handler)) + if token.Error() != nil { + return token.Error() + } + if ok := token.WaitTimeout(ps.timeout); !ok { + return errSubscribeTimeout + } + + return nil +} + +func (ps *pubsub) Unsubscribe(ctx context.Context, id, topic string) error { + if id == "" { + return ErrEmptyID + } + if topic == "" { + return ErrEmptyTopic + } + ps.mu.Lock() + defer ps.mu.Unlock() + + s, ok := ps.subscriptions[id] + if !ok || !s.contains(topic) { + return ErrNotSubscribed + } + + if err := s.unsubscribe(topic, ps.timeout); err != nil { + return err + } + ps.subscriptions[id] = s + + if len(s.topics) == 0 { + delete(ps.subscriptions, id) + } + return nil +} + +func (s *subscription) unsubscribe(topic string, timeout time.Duration) error { + if s.cancel != nil { + if err := s.cancel(); err != nil { + return err + } + } + + token := s.client.Unsubscribe(topic) + if token.Error() != nil { + return token.Error() + } + + if ok := token.WaitTimeout(timeout); !ok { + return errUnsubscribeTimeout + } + if ok := s.delete(topic); !ok { + return errUnsubscribeDeleteTopic + } + return token.Error() +} + +func newClient(address, id string, timeout time.Duration) (mqtt.Client, error) { + opts := mqtt.NewClientOptions(). + SetUsername(username). + AddBroker(address). + SetClientID(id) + client := mqtt.NewClient(opts) + token := client.Connect() + if token.Error() != nil { + return nil, token.Error() + } + + if ok := token.WaitTimeout(timeout); !ok { + return nil, ErrConnect + } + + return client, nil +} + +func (ps *pubsub) mqttHandler(h messaging.MessageHandler) mqtt.MessageHandler { + return func(_ mqtt.Client, m mqtt.Message) { + var msg messaging.Message + if err := proto.Unmarshal(m.Payload(), &msg); err != nil { + ps.logger.Warn(fmt.Sprintf("Failed to unmarshal received message: %s", err)) + return + } + + if err := h.Handle(&msg); err != nil { + ps.logger.Warn(fmt.Sprintf("Failed to handle SuperMQ message: %s", err)) + } + } +} + +// Contains checks if a topic is present. +func (s subscription) contains(topic string) bool { + return s.indexOf(topic) != -1 +} + +// Finds the index of an item in the topics. +func (s subscription) indexOf(element string) int { + for k, v := range s.topics { + if element == v { + return k + } + } + return -1 +} + +// Deletes a topic from the slice. +func (s *subscription) delete(topic string) bool { + index := s.indexOf(topic) + if index == -1 { + return false + } + topics := make([]string, len(s.topics)-1) + copy(topics[:index], s.topics[:index]) + copy(topics[index:], s.topics[index+1:]) + s.topics = topics + return true +} diff --git a/pkg/messaging/mqtt/pubsub_test.go b/pkg/messaging/mqtt/pubsub_test.go new file mode 100644 index 0000000000..9835dfb485 --- /dev/null +++ b/pkg/messaging/mqtt/pubsub_test.go @@ -0,0 +1,474 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package mqtt_test + +import ( + "context" + "errors" + "fmt" + "testing" + "time" + + "github.com/absmach/supermq/pkg/messaging" + mqttpubsub "github.com/absmach/supermq/pkg/messaging/mqtt" + mqtt "github.com/eclipse/paho.mqtt.golang" + "github.com/stretchr/testify/assert" + "google.golang.org/protobuf/proto" +) + +const ( + topic = "topic" + chansPrefix = "channels" + channel = "9b7b1b3f-b1b0-46a8-a717-b8213f9eda3b" + subtopic = "engine" + tokenTimeout = 100 * time.Millisecond +) + +var data = []byte("payload") + +// ErrFailedHandleMessage indicates that the message couldn't be handled. +var errFailedHandleMessage = errors.New("failed to handle supermq message") + +func TestPublisher(t *testing.T) { + msgChan := make(chan []byte) + + // Subscribing with topic, and with subtopic, so that we can publish messages. + client, err := newClient(address, "clientID1", brokerTimeout) + assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err)) + + token := client.Subscribe(topic, qos, func(_ mqtt.Client, m mqtt.Message) { + msgChan <- m.Payload() + }) + if ok := token.WaitTimeout(tokenTimeout); !ok { + assert.Fail(t, fmt.Sprintf("failed to subscribe to topic %s", topic)) + } + assert.Nil(t, token.Error(), fmt.Sprintf("got unexpected error: %s", token.Error())) + + token = client.Subscribe(fmt.Sprintf("%s.%s", topic, subtopic), qos, func(_ mqtt.Client, m mqtt.Message) { + msgChan <- m.Payload() + }) + if ok := token.WaitTimeout(tokenTimeout); !ok { + assert.Fail(t, fmt.Sprintf("failed to subscribe to topic %s", fmt.Sprintf("%s.%s", topic, subtopic))) + } + assert.Nil(t, token.Error(), fmt.Sprintf("got unexpected error: %s", token.Error())) + + t.Cleanup(func() { + token := client.Unsubscribe(topic, fmt.Sprintf("%s.%s", topic, subtopic)) + token.WaitTimeout(tokenTimeout) + assert.Nil(t, token.Error(), fmt.Sprintf("got unexpected error: %s", token.Error())) + + client.Disconnect(100) + }) + + // Test publish with an empty topic. + err = pubsub.Publish(context.TODO(), "", &messaging.Message{Payload: data}) + assert.Equal(t, err, mqttpubsub.ErrEmptyTopic, fmt.Sprintf("Publish with empty topic: expected: %s, got: %s", mqttpubsub.ErrEmptyTopic, err)) + + cases := []struct { + desc string + channel string + subtopic string + payload []byte + }{ + { + desc: "publish message with nil payload", + payload: nil, + }, + { + desc: "publish message with string payload", + payload: data, + }, + { + desc: "publish message with channel", + payload: data, + channel: channel, + }, + { + desc: "publish message with subtopic", + payload: data, + subtopic: subtopic, + }, + { + desc: "publish message with channel and subtopic", + payload: data, + channel: channel, + subtopic: subtopic, + }, + } + for _, tc := range cases { + expectedMsg := messaging.Message{ + Publisher: "clientID11", + Channel: tc.channel, + Subtopic: tc.subtopic, + Payload: tc.payload, + } + + err := pubsub.Publish(context.TODO(), topic, &expectedMsg) + assert.Nil(t, err, fmt.Sprintf("%s: got unexpected error: %s\n", tc.desc, err)) + + data, err := proto.Marshal(&expectedMsg) + assert.Nil(t, err, fmt.Sprintf("%s: failed to serialize protobuf error: %s\n", tc.desc, err)) + + receivedMsg := <-msgChan + if tc.payload != nil { + assert.Equal(t, expectedMsg.GetPayload(), receivedMsg, fmt.Sprintf("%s: expected %+v got %+v\n", tc.desc, data, receivedMsg)) + } + } +} + +func TestSubscribe(t *testing.T) { + msgChan := make(chan *messaging.Message) + + // Creating client to Publish messages to subscribed topic. + client, err := newClient(address, "supermq", brokerTimeout) + assert.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err)) + + t.Cleanup(func() { + client.Unsubscribe() + client.Disconnect(100) + }) + + cases := []struct { + desc string + topic string + clientID string + err error + handler messaging.MessageHandler + }{ + { + desc: "Subscribe to a topic with an ID", + topic: topic, + clientID: "clientid1", + err: nil, + handler: handler{false, "clientid1", msgChan}, + }, + { + desc: "Subscribe to the same topic with a different ID", + topic: topic, + clientID: "clientid2", + err: nil, + handler: handler{false, "clientid2", msgChan}, + }, + { + desc: "Subscribe to an already subscribed topic with an ID", + topic: topic, + clientID: "clientid1", + err: nil, + handler: handler{false, "clientid1", msgChan}, + }, + { + desc: "Subscribe to a topic with a subtopic with an ID", + topic: fmt.Sprintf("%s.%s", topic, subtopic), + clientID: "clientid1", + err: nil, + handler: handler{false, "clientid1", msgChan}, + }, + { + desc: "Subscribe to an already subscribed topic with a subtopic with an ID", + topic: fmt.Sprintf("%s.%s", topic, subtopic), + clientID: "clientid1", + err: nil, + handler: handler{false, "clientid1", msgChan}, + }, + { + desc: "Subscribe to an empty topic with an ID", + topic: "", + clientID: "clientid1", + err: mqttpubsub.ErrEmptyTopic, + handler: handler{false, "clientid1", msgChan}, + }, + { + desc: "Subscribe to a topic with empty id", + topic: topic, + clientID: "", + err: mqttpubsub.ErrEmptyID, + handler: handler{false, "", msgChan}, + }, + } + for _, tc := range cases { + subCfg := messaging.SubscriberConfig{ + ID: tc.clientID, + Topic: tc.topic, + Handler: tc.handler, + } + err = pubsub.Subscribe(context.TODO(), subCfg) + assert.Equal(t, err, tc.err, fmt.Sprintf("%s: expected: %s, but got: %s", tc.desc, err, tc.err)) + + if tc.err == nil { + expectedMsg := messaging.Message{ + Publisher: "clientID1", + Channel: channel, + Subtopic: subtopic, + Payload: data, + } + data, err := proto.Marshal(&expectedMsg) + assert.Nil(t, err, fmt.Sprintf("%s: failed to serialize protobuf error: %s\n", tc.desc, err)) + + token := client.Publish(tc.topic, qos, false, data) + token.WaitTimeout(tokenTimeout) + assert.Nil(t, token.Error(), fmt.Sprintf("got unexpected error: %s", token.Error())) + + receivedMsg := <-msgChan + assert.Equal(t, expectedMsg.Channel, receivedMsg.Channel, fmt.Sprintf("%s: expected %+v got %+v\n", tc.desc, &expectedMsg, receivedMsg)) + assert.Equal(t, expectedMsg.Created, receivedMsg.Created, fmt.Sprintf("%s: expected %+v got %+v\n", tc.desc, &expectedMsg, receivedMsg)) + assert.Equal(t, expectedMsg.Protocol, receivedMsg.Protocol, fmt.Sprintf("%s: expected %+v got %+v\n", tc.desc, &expectedMsg, receivedMsg)) + assert.Equal(t, expectedMsg.Publisher, receivedMsg.Publisher, fmt.Sprintf("%s: expected %+v got %+v\n", tc.desc, &expectedMsg, receivedMsg)) + assert.Equal(t, expectedMsg.Subtopic, receivedMsg.Subtopic, fmt.Sprintf("%s: expected %+v got %+v\n", tc.desc, &expectedMsg, receivedMsg)) + assert.Equal(t, expectedMsg.Payload, receivedMsg.Payload, fmt.Sprintf("%s: expected %+v got %+v\n", tc.desc, &expectedMsg, receivedMsg)) + } + } +} + +func TestPubSub(t *testing.T) { + msgChan := make(chan *messaging.Message) + + cases := []struct { + desc string + topic string + clientID string + err error + handler messaging.MessageHandler + }{ + { + desc: "Subscribe to a topic with an ID", + topic: topic, + clientID: "clientid7", + err: nil, + handler: handler{false, "clientid7", msgChan}, + }, + { + desc: "Subscribe to the same topic with a different ID", + topic: topic, + clientID: "clientid8", + err: nil, + handler: handler{false, "clientid8", msgChan}, + }, + { + desc: "Subscribe to a topic with a subtopic with an ID", + topic: fmt.Sprintf("%s.%s", topic, subtopic), + clientID: "clientid7", + err: nil, + handler: handler{false, "clientid7", msgChan}, + }, + { + desc: "Subscribe to an empty topic with an ID", + topic: "", + clientID: "clientid7", + err: mqttpubsub.ErrEmptyTopic, + handler: handler{false, "clientid7", msgChan}, + }, + { + desc: "Subscribe to a topic with empty id", + topic: topic, + clientID: "", + err: mqttpubsub.ErrEmptyID, + handler: handler{false, "", msgChan}, + }, + } + for _, tc := range cases { + subCfg := messaging.SubscriberConfig{ + ID: tc.clientID, + Topic: tc.topic, + Handler: tc.handler, + } + err := pubsub.Subscribe(context.TODO(), subCfg) + assert.Equal(t, err, tc.err, fmt.Sprintf("%s: expected: %s, but got: %s", tc.desc, err, tc.err)) + + if tc.err == nil { + // Use pubsub to subscribe to a topic, and then publish messages to that topic. + expectedMsg := messaging.Message{ + Publisher: "clientID", + Channel: channel, + Subtopic: subtopic, + Payload: data, + } + data, err := proto.Marshal(&expectedMsg) + assert.Nil(t, err, fmt.Sprintf("%s: failed to serialize protobuf error: %s\n", tc.desc, err)) + + msg := messaging.Message{ + Payload: data, + } + // Publish message, and then receive it on message channel. + err = pubsub.Publish(context.TODO(), topic, &msg) + assert.Nil(t, err, fmt.Sprintf("%s: got unexpected error: %s\n", tc.desc, err)) + + receivedMsg := <-msgChan + assert.Equal(t, expectedMsg.Channel, receivedMsg.Channel, fmt.Sprintf("%s: expected %+v got %+v\n", tc.desc, &expectedMsg, receivedMsg)) + assert.Equal(t, expectedMsg.Created, receivedMsg.Created, fmt.Sprintf("%s: expected %+v got %+v\n", tc.desc, &expectedMsg, receivedMsg)) + assert.Equal(t, expectedMsg.Protocol, receivedMsg.Protocol, fmt.Sprintf("%s: expected %+v got %+v\n", tc.desc, &expectedMsg, receivedMsg)) + assert.Equal(t, expectedMsg.Publisher, receivedMsg.Publisher, fmt.Sprintf("%s: expected %+v got %+v\n", tc.desc, &expectedMsg, receivedMsg)) + assert.Equal(t, expectedMsg.Subtopic, receivedMsg.Subtopic, fmt.Sprintf("%s: expected %+v got %+v\n", tc.desc, &expectedMsg, receivedMsg)) + assert.Equal(t, expectedMsg.Payload, receivedMsg.Payload, fmt.Sprintf("%s: expected %+v got %+v\n", tc.desc, &expectedMsg, receivedMsg)) + } + } +} + +func TestUnsubscribe(t *testing.T) { + msgChan := make(chan *messaging.Message) + + cases := []struct { + desc string + topic string + clientID string + err error + subscribe bool // True for subscribe and false for unsubscribe. + handler messaging.MessageHandler + }{ + { + desc: "Subscribe to a topic with an ID", + topic: fmt.Sprintf("%s.%s", chansPrefix, topic), + clientID: "clientid4", + err: nil, + subscribe: true, + handler: handler{false, "clientid4", msgChan}, + }, + { + desc: "Subscribe to the same topic with a different ID", + topic: fmt.Sprintf("%s.%s", chansPrefix, topic), + clientID: "clientid9", + err: nil, + subscribe: true, + handler: handler{false, "clientid9", msgChan}, + }, + { + desc: "Unsubscribe from a topic with an ID", + topic: fmt.Sprintf("%s.%s", chansPrefix, topic), + clientID: "clientid4", + err: nil, + subscribe: false, + handler: handler{false, "clientid4", msgChan}, + }, + { + desc: "Unsubscribe from same topic with different ID", + topic: fmt.Sprintf("%s.%s", chansPrefix, topic), + clientID: "clientid9", + err: nil, + subscribe: false, + handler: handler{false, "clientid9", msgChan}, + }, + { + desc: "Unsubscribe from a non-existent topic with an ID", + topic: "h", + clientID: "clientid4", + err: mqttpubsub.ErrNotSubscribed, + subscribe: false, + handler: handler{false, "clientid4", msgChan}, + }, + { + desc: "Unsubscribe from an already unsubscribed topic with an ID", + topic: fmt.Sprintf("%s.%s", chansPrefix, topic), + clientID: "clientid4", + err: mqttpubsub.ErrNotSubscribed, + subscribe: false, + handler: handler{false, "clientid4", msgChan}, + }, + { + desc: "Subscribe to a topic with a subtopic with an ID", + topic: fmt.Sprintf("%s.%s.%s", chansPrefix, topic, subtopic), + clientID: "clientidd4", + err: nil, + subscribe: true, + handler: handler{false, "clientidd4", msgChan}, + }, + { + desc: "Unsubscribe from a topic with a subtopic with an ID", + topic: fmt.Sprintf("%s.%s.%s", chansPrefix, topic, subtopic), + clientID: "clientidd4", + err: nil, + subscribe: false, + handler: handler{false, "clientidd4", msgChan}, + }, + { + desc: "Unsubscribe from an already unsubscribed topic with a subtopic with an ID", + topic: fmt.Sprintf("%s.%s.%s", chansPrefix, topic, subtopic), + clientID: "clientid4", + err: mqttpubsub.ErrNotSubscribed, + subscribe: false, + handler: handler{false, "clientid4", msgChan}, + }, + { + desc: "Unsubscribe from an empty topic with an ID", + topic: "", + clientID: "clientid4", + err: mqttpubsub.ErrEmptyTopic, + subscribe: false, + handler: handler{false, "clientid4", msgChan}, + }, + { + desc: "Unsubscribe from a topic with empty ID", + topic: fmt.Sprintf("%s.%s", chansPrefix, topic), + clientID: "", + err: mqttpubsub.ErrEmptyID, + subscribe: false, + handler: handler{false, "", msgChan}, + }, + { + desc: "Subscribe to a new topic with an ID", + topic: fmt.Sprintf("%s.%s", chansPrefix, topic+"2"), + clientID: "clientid55", + err: nil, + subscribe: true, + handler: handler{true, "clientid5", msgChan}, + }, + { + desc: "Unsubscribe from a topic with an ID with failing handler", + topic: fmt.Sprintf("%s.%s", chansPrefix, topic+"2"), + clientID: "clientid55", + err: errFailedHandleMessage, + subscribe: false, + handler: handler{true, "clientid5", msgChan}, + }, + { + desc: "Subscribe to a new topic with subtopic with an ID", + topic: fmt.Sprintf("%s.%s.%s", chansPrefix, topic+"2", subtopic), + clientID: "clientid55", + err: nil, + subscribe: true, + handler: handler{true, "clientid5", msgChan}, + }, + { + desc: "Unsubscribe from a topic with subtopic with an ID with failing handler", + topic: fmt.Sprintf("%s.%s.%s", chansPrefix, topic+"2", subtopic), + clientID: "clientid55", + err: errFailedHandleMessage, + subscribe: false, + handler: handler{true, "clientid5", msgChan}, + }, + } + for _, tc := range cases { + subCfg := messaging.SubscriberConfig{ + ID: tc.clientID, + Topic: tc.topic, + Handler: tc.handler, + } + switch tc.subscribe { + case true: + err := pubsub.Subscribe(context.TODO(), subCfg) + assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected: %s, but got: %s", tc.desc, tc.err, err)) + default: + err := pubsub.Unsubscribe(context.TODO(), tc.clientID, tc.topic) + assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected: %s, but got: %s", tc.desc, tc.err, err)) + } + } +} + +type handler struct { + fail bool + publisher string + msgChan chan *messaging.Message +} + +func (h handler) Handle(msg *messaging.Message) error { + if msg.GetPublisher() != h.publisher { + h.msgChan <- msg + } + return nil +} + +func (h handler) Cancel() error { + if h.fail { + return errFailedHandleMessage + } + return nil +} diff --git a/pkg/messaging/mqtt/setup_test.go b/pkg/messaging/mqtt/setup_test.go new file mode 100644 index 0000000000..9a01af691b --- /dev/null +++ b/pkg/messaging/mqtt/setup_test.go @@ -0,0 +1,121 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package mqtt_test + +import ( + "fmt" + "log" + "log/slog" + "os" + "os/signal" + "syscall" + "testing" + "time" + + smqlog "github.com/absmach/supermq/logger" + "github.com/absmach/supermq/pkg/messaging" + mqttpubsub "github.com/absmach/supermq/pkg/messaging/mqtt" + mqtt "github.com/eclipse/paho.mqtt.golang" + "github.com/ory/dockertest/v3" + "github.com/ory/dockertest/v3/docker" +) + +var ( + pubsub messaging.PubSub + logger *slog.Logger + address string +) + +const ( + username = "supermq-mqtt" + qos = 2 + port = "1883/tcp" + brokerTimeout = 30 * time.Second + poolMaxWait = 120 * time.Second +) + +func TestMain(m *testing.M) { + pool, err := dockertest.NewPool("") + if err != nil { + log.Fatalf("Could not connect to docker: %s", err) + } + + container, err := pool.RunWithOptions(&dockertest.RunOptions{ + Repository: "eclipse-mosquitto", + Tag: "1.6.15", + }, func(config *docker.HostConfig) { + config.AutoRemove = true + config.RestartPolicy = docker.RestartPolicy{Name: "no"} + }) + if err != nil { + log.Fatalf("Could not start container: %s", err) + } + + handleInterrupt(pool, container) + + address = fmt.Sprintf("%s:%s", "localhost", container.GetPort(port)) + pool.MaxWait = poolMaxWait + + logger, err = smqlog.New(os.Stdout, "debug") + if err != nil { + log.Fatal(err.Error()) + } + + if err := pool.Retry(func() error { + pubsub, err = mqttpubsub.NewPubSub(address, 2, brokerTimeout, logger) + return err + }); err != nil { + log.Fatalf("Could not connect to docker: %s", err) + } + + code := m.Run() + if err := pool.Purge(container); err != nil { + log.Fatalf("Could not purge container: %s", err) + } + + os.Exit(code) + + defer func() { + err = pubsub.Close() + if err != nil { + log.Fatal(err.Error()) + } + }() +} + +func handleInterrupt(pool *dockertest.Pool, container *dockertest.Resource) { + c := make(chan os.Signal, 2) + signal.Notify(c, os.Interrupt, syscall.SIGTERM) + go func() { + <-c + if err := pool.Purge(container); err != nil { + log.Fatalf("Could not purge container: %s", err) + } + os.Exit(0) + }() +} + +func newClient(address, id string, timeout time.Duration) (mqtt.Client, error) { + opts := mqtt.NewClientOptions(). + SetUsername(username). + AddBroker(address). + SetClientID(id) + + client := mqtt.NewClient(opts) + token := client.Connect() + if token.Error() != nil { + return nil, token.Error() + } + + ok := token.WaitTimeout(timeout) + if !ok { + return nil, mqttpubsub.ErrConnect + } + + if token.Error() != nil { + return nil, token.Error() + } + + return client, nil +} From 2680a72dbb4210df285e0a5c48794ecbe88039f1 Mon Sep 17 00:00:00 2001 From: Rodney Osodo Date: Mon, 10 Feb 2025 14:05:54 +0300 Subject: [PATCH 02/13] feat: replace NATS with RabbitMQ as the default MQTT broker Signed-off-by: Rodney Osodo --- .github/workflows/check-license.yaml | 2 +- docker/.env | 19 +- docker/README.md | 14 +- docker/docker-compose.yml | 26 +- docker/rabbitmq/enabled_plugins | 1 + docker/rabbitmq/rabbitmq.conf | 15 ++ docker/vernemq/Dockerfile | 56 ----- docker/vernemq/bin/vernemq.sh | 352 --------------------------- docker/vernemq/files/vm.args | 15 -- 9 files changed, 50 insertions(+), 450 deletions(-) create mode 100644 docker/rabbitmq/enabled_plugins create mode 100644 docker/rabbitmq/rabbitmq.conf delete mode 100644 docker/vernemq/Dockerfile delete mode 100755 docker/vernemq/bin/vernemq.sh delete mode 100644 docker/vernemq/files/vm.args diff --git a/.github/workflows/check-license.yaml b/.github/workflows/check-license.yaml index 7b97d2b86b..2977dd9b1e 100644 --- a/.github/workflows/check-license.yaml +++ b/.github/workflows/check-license.yaml @@ -21,7 +21,7 @@ jobs: - name: Check License Header run: | CHECK="" - for file in $(grep -rl --exclude-dir={.git,build,**vernemq**} \ + for file in $(grep -rl --exclude-dir={.git,build} \ --exclude=\*.{crt,key,pem,zed,hcl,md,json,csv,mod,sum,tmpl,args} \ --exclude={CODEOWNERS,LICENSE,MAINTAINERS} \ .); do diff --git a/docker/.env b/docker/.env index b53d986f99..95732c2539 100644 --- a/docker/.env +++ b/docker/.env @@ -30,28 +30,25 @@ SMQ_RABBITMQ_PASS=supermq SMQ_RABBITMQ_COOKIE=supermq SMQ_RABBITMQ_VHOST=/ SMQ_RABBITMQ_URL=amqp://${SMQ_RABBITMQ_USER}:${SMQ_RABBITMQ_PASS}@rabbitmq:${SMQ_RABBITMQ_PORT}${SMQ_RABBITMQ_VHOST} +SMQ_RABBITMQ_MQTT_QOS=2 +SMQ_RABBITMQ_WS_TARGET_PATH=/ ## Message Broker SMQ_MESSAGE_BROKER_TYPE=nats SMQ_MESSAGE_BROKER_URL=${SMQ_NATS_URL} -## VERNEMQ -SMQ_DOCKER_VERNEMQ_ALLOW_ANONYMOUS=on -SMQ_DOCKER_VERNEMQ_LOG__CONSOLE__LEVEL=error -SMQ_VERNEMQ_HEALTH_CHECK=http://vernemq:8888/health -SMQ_VERNEMQ_WS_TARGET_PATH=/mqtt -SMQ_VERNEMQ_MQTT_QOS=2 - ## MQTT Broker -SMQ_MQTT_BROKER_TYPE=vernemq -SMQ_MQTT_BROKER_HEALTH_CHECK=${SMQ_VERNEMQ_HEALTH_CHECK} -SMQ_MQTT_ADAPTER_MQTT_QOS=${SMQ_VERNEMQ_MQTT_QOS} +SMQ_MQTT_BROKER_TYPE=rabbitmq +SMQ_MQTT_BROKER_HEALTH_CHECK= +SMQ_MQTT_ADAPTER_MQTT_QOS=${SMQ_RABBITMQ_MQTT_QOS} SMQ_MQTT_ADAPTER_MQTT_TARGET_HOST=${SMQ_MQTT_BROKER_TYPE} SMQ_MQTT_ADAPTER_MQTT_TARGET_PORT=1883 +SMQ_MQTT_ADAPTER_MQTT_TARGET_USERNAME=${SMQ_RABBITMQ_USER} +SMQ_MQTT_ADAPTER_MQTT_TARGET_PASSWORD=${SMQ_RABBITMQ_PASS} SMQ_MQTT_ADAPTER_MQTT_TARGET_HEALTH_CHECK=${SMQ_MQTT_BROKER_HEALTH_CHECK} SMQ_MQTT_ADAPTER_WS_TARGET_HOST=${SMQ_MQTT_BROKER_TYPE} SMQ_MQTT_ADAPTER_WS_TARGET_PORT=8080 -SMQ_MQTT_ADAPTER_WS_TARGET_PATH=${SMQ_VERNEMQ_WS_TARGET_PATH} +SMQ_MQTT_ADAPTER_WS_TARGET_PATH=${SMQ_RABBITMQ_WS_TARGET_PATH} ## Redis SMQ_REDIS_TCP_PORT=6379 diff --git a/docker/README.md b/docker/README.md index 938cab6f1d..ecf02df9a4 100644 --- a/docker/README.md +++ b/docker/README.md @@ -26,21 +26,21 @@ To pull docker images from a specific release you need to change the value of `S SuperMQ supports configurable MQTT broker and Message broker, which also acts as an events store. SuperMQ uses two types of brokers: -1. MQTT_BROKER: Handles MQTT communication between MQTT adapters and message broker. This can either be 'VerneMQ' or 'NATS'. +1. MQTT_BROKER: Handles MQTT communication between MQTT adapters and message broker. This can either be 'RabbitMQ' or 'NATS'. 2. MESSAGE_BROKER: Manages message exchange between SuperMQ core, optional, and external services. This can either be 'NATS' or 'RabbitMQ'. This is used to store messages for distributed processing. Events store: This is used by SuperMQ services to store events for distributed processing. SuperMQ uses a single service to be the message broker and events store. This can either be 'NATS' or 'RabbitMQ'. Redis can also be used as an events store, but it requires a message broker to be deployed along with it for message exchange. This is the same as MESSAGE_BROKER. This can either be 'NATS' or 'RabbitMQ' or 'Redis'. If Redis is used as an events store, then RabbitMQ or NATS is used as a message broker. -The current deployment strategy for SuperMQ in `docker/docker-compose.yml` is to use VerneMQ as a MQTT_BROKER and NATS as a MESSAGE_BROKER and EVENTS_STORE. +The current deployment strategy for SuperMQ in `docker/docker-compose.yml` is to use RabbitMQ as a MQTT_BROKER and NATS as a MESSAGE_BROKER and EVENTS_STORE. Therefore, the following combinations are possible: -- MQTT_BROKER: VerneMQ, MESSAGE_BROKER: NATS, EVENTS_STORE: NATS -- MQTT_BROKER: VerneMQ, MESSAGE_BROKER: NATS, EVENTS_STORE: Redis -- MQTT_BROKER: VerneMQ, MESSAGE_BROKER: RabbitMQ, EVENTS_STORE: RabbitMQ -- MQTT_BROKER: VerneMQ, MESSAGE_BROKER: RabbitMQ, EVENTS_STORE: Redis +- MQTT_BROKER: RabbitMQ, MESSAGE_BROKER: NATS, EVENTS_STORE: NATS +- MQTT_BROKER: RabbitMQ, MESSAGE_BROKER: NATS, EVENTS_STORE: Redis +- MQTT_BROKER: RabbitMQ, MESSAGE_BROKER: RabbitMQ, EVENTS_STORE: RabbitMQ +- MQTT_BROKER: RabbitMQ, MESSAGE_BROKER: RabbitMQ, EVENTS_STORE: Redis - MQTT_BROKER: NATS, MESSAGE_BROKER: RabbitMQ, EVENTS_STORE: RabbitMQ - MQTT_BROKER: NATS, MESSAGE_BROKER: RabbitMQ, EVENTS_STORE: Redis - MQTT_BROKER: NATS, MESSAGE_BROKER: NATS, EVENTS_STORE: NATS @@ -70,7 +70,7 @@ SMQ_ES_TYPE=redis SMQ_ES_URL=${SMQ_REDIS_URL} ``` -For MQTT broker other than VerneMQ, you would need to change the `docker/.env`. For example, to use NATS as a MQTT broker: +For MQTT broker other than RabbitMQ, you would need to change the `docker/.env`. For example, to use NATS as a MQTT broker: ```env SMQ_MQTT_BROKER_TYPE=nats diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 39cbf9772f..2d9ef7e71e 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -886,7 +886,7 @@ services: container_name: supermq-mqtt depends_on: - clients - - vernemq + - rabbitmq - nats restart: on-failure environment: @@ -894,6 +894,8 @@ services: SMQ_MQTT_ADAPTER_MQTT_PORT: ${SMQ_MQTT_ADAPTER_MQTT_PORT} SMQ_MQTT_ADAPTER_MQTT_TARGET_HOST: ${SMQ_MQTT_ADAPTER_MQTT_TARGET_HOST} SMQ_MQTT_ADAPTER_MQTT_TARGET_PORT: ${SMQ_MQTT_ADAPTER_MQTT_TARGET_PORT} + SMQ_MQTT_ADAPTER_MQTT_TARGET_USERNAME: ${SMQ_MQTT_ADAPTER_MQTT_TARGET_USERNAME} + SMQ_MQTT_ADAPTER_MQTT_TARGET_PASSWORD: ${SMQ_MQTT_ADAPTER_MQTT_TARGET_PASSWORD} SMQ_MQTT_ADAPTER_FORWARDER_TIMEOUT: ${SMQ_MQTT_ADAPTER_FORWARDER_TIMEOUT} SMQ_MQTT_ADAPTER_MQTT_TARGET_HEALTH_CHECK: ${SMQ_MQTT_ADAPTER_MQTT_TARGET_HEALTH_CHECK} SMQ_MQTT_ADAPTER_MQTT_QOS: ${SMQ_MQTT_ADAPTER_MQTT_QOS} @@ -1207,17 +1209,25 @@ services: bind: create_host_path: true - vernemq: - image: supermq/vernemq:${SMQ_RELEASE_TAG} - container_name: supermq-vernemq + rabbitmq: + image: rabbitmq:4.0.5-management-alpine + container_name: supermq-rabbitmq restart: on-failure environment: - DOCKER_VERNEMQ_ALLOW_ANONYMOUS: ${SMQ_DOCKER_VERNEMQ_ALLOW_ANONYMOUS} - DOCKER_VERNEMQ_LOG__CONSOLE__LEVEL: ${SMQ_DOCKER_VERNEMQ_LOG__CONSOLE__LEVEL} + RABBITMQ_ERLANG_COOKIE: ${SMQ_RABBITMQ_COOKIE} + RABBITMQ_DEFAULT_USER: ${SMQ_RABBITMQ_USER} + RABBITMQ_DEFAULT_PASS: ${SMQ_RABBITMQ_PASS} + RABBITMQ_DEFAULT_VHOST: ${SMQ_RABBITMQ_VHOST} + RABBITMQ_CONFIG_FILES: /etc/rabbitmq/conf.d/ + ports: + - ${SMQ_RABBITMQ_PORT}:${SMQ_RABBITMQ_PORT} + - ${SMQ_RABBITMQ_HTTP_PORT}:${SMQ_RABBITMQ_HTTP_PORT} + volumes: + - ./rabbitmq/enabled_plugins:/etc/rabbitmq/enabled_plugins + - ./rabbitmq/rabbitmq.conf:/etc/rabbitmq/conf.d/10-defaults.conf + - supermq-mqtt-broker-volume:/var/lib/rabbitmq networks: - supermq-base-net - volumes: - - supermq-mqtt-broker-volume:/var/lib/vernemq nats: image: nats:2.10.9-alpine diff --git a/docker/rabbitmq/enabled_plugins b/docker/rabbitmq/enabled_plugins new file mode 100644 index 0000000000..5358cb0171 --- /dev/null +++ b/docker/rabbitmq/enabled_plugins @@ -0,0 +1 @@ +[rabbitmq_management,rabbitmq_mqtt]. diff --git a/docker/rabbitmq/rabbitmq.conf b/docker/rabbitmq/rabbitmq.conf new file mode 100644 index 0000000000..31e326c6b1 --- /dev/null +++ b/docker/rabbitmq/rabbitmq.conf @@ -0,0 +1,15 @@ +## DEFAULT SETTINGS ARE NOT MEANT TO BE TAKEN STRAIGHT INTO PRODUCTION +## see https://www.rabbitmq.com/configure.html for further information +## on configuring RabbitMQ + +## allow access to the guest user from anywhere on the network +## https://www.rabbitmq.com/access-control.html#loopback-users +## https://www.rabbitmq.com/production-checklist.html#users +loopback_users.guest = false + +## Send all logs to stdout/TTY. Necessary to see logs when running via +## a container +log.console = true + +## Enable anonymous connection +mqtt.allow_anonymous = true diff --git a/docker/vernemq/Dockerfile b/docker/vernemq/Dockerfile deleted file mode 100644 index 76152b1f51..0000000000 --- a/docker/vernemq/Dockerfile +++ /dev/null @@ -1,56 +0,0 @@ -# Copyright (c) Abstract Machines -# SPDX-License-Identifier: Apache-2.0 - -# Builder -FROM erlang:25.3.2.8-alpine AS builder -RUN apk add --update git build-base bsd-compat-headers openssl-dev snappy-dev curl \ - && git clone -b 1.13.0 https://github.com/vernemq/vernemq \ - && cd vernemq \ - && make -j 16 rel - -# Executor -FROM alpine:3.19 - -COPY --from=builder /vernemq/_build/default/rel / - -RUN apk --no-cache --update --available upgrade && \ - apk add --no-cache ncurses-libs openssl libstdc++ jq curl bash snappy-dev && \ - addgroup --gid 10000 vernemq && \ - adduser --uid 10000 -H -D -G vernemq -h /vernemq vernemq && \ - install -d -o vernemq -g vernemq /vernemq - -# Defaults -ENV DOCKER_VERNEMQ_KUBERNETES_LABEL_SELECTOR="app=vernemq" \ - DOCKER_VERNEMQ_LOG__CONSOLE=console \ - PATH="/vernemq/bin:$PATH" \ - VERNEMQ_VERSION="1.13.0" - -WORKDIR /vernemq - -COPY --chown=10000:10000 bin/vernemq.sh /usr/sbin/start_vernemq -COPY --chown=10000:10000 files/vm.args /vernemq/etc/vm.args - -RUN chown -R 10000:10000 /vernemq && \ - ln -s /vernemq/etc /etc/vernemq && \ - ln -s /vernemq/data /var/lib/vernemq && \ - ln -s /vernemq/log /var/log/vernemq - -# Ports -# 1883 MQTT -# 8883 MQTT/SSL -# 8080 MQTT WebSockets -# 44053 VerneMQ Message Distribution -# 4369 EPMD - Erlang Port Mapper Daemon -# 8888 Health, API, Prometheus Metrics -# 9100 9101 9102 9103 9104 9105 9106 9107 9108 9109 Specific Distributed Erlang Port Range - -EXPOSE 1883 8883 8080 44053 4369 8888 \ - 9100 9101 9102 9103 9104 9105 9106 9107 9108 9109 - - -VOLUME ["/vernemq/log", "/vernemq/data", "/vernemq/etc"] - -HEALTHCHECK CMD vernemq ping | grep -q pong - -USER vernemq -CMD ["start_vernemq"] \ No newline at end of file diff --git a/docker/vernemq/bin/vernemq.sh b/docker/vernemq/bin/vernemq.sh deleted file mode 100755 index 4c990dafd1..0000000000 --- a/docker/vernemq/bin/vernemq.sh +++ /dev/null @@ -1,352 +0,0 @@ -#!/usr/bin/env sh - -NET_INTERFACE=$(route | grep '^default' | grep -o '[^ ]*$') -NET_INTERFACE=${DOCKER_NET_INTERFACE:-${NET_INTERFACE}} -IP_ADDRESS=$(ip -4 addr show ${NET_INTERFACE} | grep -oE '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' | sed -e "s/^[[:space:]]*//" | head -n 1) -IP_ADDRESS=${DOCKER_IP_ADDRESS:-${IP_ADDRESS}} - -VERNEMQ_ETC_DIR="/vernemq/etc" -VERNEMQ_VM_ARGS_FILE="${VERNEMQ_ETC_DIR}/vm.args" -VERNEMQ_CONF_FILE="${VERNEMQ_ETC_DIR}/vernemq.conf" -VERNEMQ_CONF_LOCAL_FILE="${VERNEMQ_ETC_DIR}/vernemq.conf.local" - -SECRETS_KUBERNETES_DIR="/var/run/secrets/kubernetes.io/serviceaccount" - -# Function to check istio readiness -istio_health() { - cmd=$(curl -s http://localhost:15021/healthz/ready > /dev/null) - status=$? - return $status -} - -# Ensure we have all files and needed directory write permissions -if [ ! -d ${VERNEMQ_ETC_DIR} ]; then - echo "Configuration directory at ${VERNEMQ_ETC_DIR} does not exist, exiting" >&2 - exit 1 -fi -if [ ! -f ${VERNEMQ_VM_ARGS_FILE} ]; then - echo "ls -l ${VERNEMQ_ETC_DIR}" - ls -l ${VERNEMQ_ETC_DIR} - echo "###" >&2 - echo "### Configuration file ${VERNEMQ_VM_ARGS_FILE} does not exist, exiting" >&2 - echo "###" >&2 - exit 1 -fi -if [ ! -w ${VERNEMQ_VM_ARGS_FILE} ]; then - echo "# whoami" - whoami - echo "# ls -l ${VERNEMQ_ETC_DIR}" - ls -l ${VERNEMQ_ETC_DIR} - echo "###" >&2 - echo "### Configuration file ${VERNEMQ_VM_ARGS_FILE} exists, but there are no write permissions! Exiting." >&2 - echo "###" >&2 - exit 1 -fi -if [ ! -s ${VERNEMQ_VM_ARGS_FILE} ]; then - echo "ls -l ${VERNEMQ_ETC_DIR}" - ls -l ${VERNEMQ_ETC_DIR} - echo "###" >&2 - echo "### Configuration file ${VERNEMQ_VM_ARGS_FILE} is empty! This will not work." >&2 - echo "### Exiting now." >&2 - echo "###" >&2 - exit 1 -fi - -# Ensure the Erlang node name is set correctly -if env | grep "DOCKER_VERNEMQ_NODENAME" -q; then - sed -i.bak -r "s/-name VerneMQ@.+/-name VerneMQ@${DOCKER_VERNEMQ_NODENAME}/" ${VERNEMQ_VM_ARGS_FILE} -else - if [ -n "$DOCKER_VERNEMQ_SWARM" ]; then - NODENAME=$(hostname -i) - sed -i.bak -r "s/VerneMQ@.+/VerneMQ@${NODENAME}/" ${VERNEMQ_VM_ARGS_FILE} - else - sed -i.bak -r "s/-name VerneMQ@.+/-name VerneMQ@${IP_ADDRESS}/" ${VERNEMQ_VM_ARGS_FILE} - fi -fi - -if env | grep "DOCKER_VERNEMQ_DISCOVERY_NODE" -q; then - discovery_node=$DOCKER_VERNEMQ_DISCOVERY_NODE - if [ -n "$DOCKER_VERNEMQ_SWARM" ]; then - tmp='' - while [[ -z "$tmp" ]]; do - tmp=$(getent hosts tasks.$discovery_node | awk '{print $1}' | head -n 1) - sleep 1 - done - discovery_node=$tmp - fi - if [ -n "$DOCKER_VERNEMQ_COMPOSE" ]; then - tmp='' - while [[ -z "$tmp" ]]; do - tmp=$(getent hosts $discovery_node | awk '{print $1}' | head -n 1) - sleep 1 - done - discovery_node=$tmp - fi - - sed -i.bak -r "/-eval.+/d" ${VERNEMQ_VM_ARGS_FILE} - echo "-eval \"vmq_server_cmd:node_join('VerneMQ@$discovery_node')\"" >> ${VERNEMQ_VM_ARGS_FILE} -fi - -# If you encounter "SSL certification error (subject name does not match the host name)", you may try to set DOCKER_VERNEMQ_KUBERNETES_INSECURE to "1". -insecure="" -if env | grep "DOCKER_VERNEMQ_KUBERNETES_INSECURE" -q; then - echo "Using curl with \"--insecure\" argument to access kubernetes API without matching SSL certificate" - insecure="--insecure" -fi - -if env | grep "DOCKER_VERNEMQ_KUBERNETES_ISTIO_ENABLED" -q; then - istio_health - while [ $status != 0 ]; do - istio_health - sleep 1 - done - echo "Istio ready" -fi - -# Function to call a HTTP GET request on the given URL Path, using the hostname -# of the current k8s cluster name. Usage: "k8sCurlGet /my/path" -function k8sCurlGet () { - local urlPath=$1 - - local hostname="kubernetes.default.svc.${DOCKER_VERNEMQ_KUBERNETES_CLUSTER_NAME}" - local certsFile="${SECRETS_KUBERNETES_DIR}/ca.crt" - local token=$(cat ${SECRETS_KUBERNETES_DIR}/token) - local header="Authorization: Bearer ${token}" - local url="https://${hostname}/${urlPath}" - - curl -sS ${insecure} --cacert ${certsFile} -H "${header}" ${url} \ - || ( echo "### Error on accessing URL ${url}" ) -} - -DOCKER_VERNEMQ_KUBERNETES_CLUSTER_NAME=${DOCKER_VERNEMQ_KUBERNETES_CLUSTER_NAME:-cluster.local} -if [ -d "${SECRETS_KUBERNETES_DIR}" ] ; then - # Let's get the namespace if it isn't set - DOCKER_VERNEMQ_KUBERNETES_NAMESPACE=${DOCKER_VERNEMQ_KUBERNETES_NAMESPACE:-$(cat "${SECRETS_KUBERNETES_DIR}/namespace")} - - # Check the API access that will be needed in the TERM signal handler - podResponse=$(k8sCurlGet api/v1/namespaces/${DOCKER_VERNEMQ_KUBERNETES_NAMESPACE}/pods/$(hostname) ) - statefulSetName=$(echo ${podResponse} | jq -r '.metadata.ownerReferences[0].name') - statefulSetPath="apis/apps/v1/namespaces/${DOCKER_VERNEMQ_KUBERNETES_NAMESPACE}/statefulsets/${statefulSetName}" - statefulSetResponse=$(k8sCurlGet ${statefulSetPath} ) - isCodeForbidden=$(echo ${statefulSetResponse} | jq '.code == 403') - if [[ ${isCodeForbidden} == "true" ]]; then - echo "Permission error: Cannot access URL ${statefulSetPath}: $(echo ${statefulSetResponse} | jq '.reason,.code,.message')" - exit 1 - else - numReplicas=$(echo ${statefulSetResponse} | jq '.status.replicas') - echo "Permissions ok: Our pod $(hostname) belongs to StatefulSet ${statefulSetName} with ${numReplicas} replicas" - fi -fi - -# Set up kubernetes node discovery -start_join_cluster=0 -if env | grep "DOCKER_VERNEMQ_DISCOVERY_KUBERNETES" -q; then - # Let's set our nodename correctly - # https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#list-pod-v1-core - podList=$(k8sCurlGet "api/v1/namespaces/${DOCKER_VERNEMQ_KUBERNETES_NAMESPACE}/pods?labelSelector=${DOCKER_VERNEMQ_KUBERNETES_LABEL_SELECTOR}") - VERNEMQ_KUBERNETES_SUBDOMAIN=${DOCKER_VERNEMQ_KUBERNETES_SUBDOMAIN:-$(echo ${podList} | jq '.items[0].spec.subdomain' | tr '\n' '"' | sed 's/"//g')} - if [[ $VERNEMQ_KUBERNETES_SUBDOMAIN == "null" ]]; then - VERNEMQ_KUBERNETES_HOSTNAME=${MY_POD_NAME}.${DOCKER_VERNEMQ_KUBERNETES_NAMESPACE}.svc.${DOCKER_VERNEMQ_KUBERNETES_CLUSTER_NAME} - else - VERNEMQ_KUBERNETES_HOSTNAME=${MY_POD_NAME}.${VERNEMQ_KUBERNETES_SUBDOMAIN}.${DOCKER_VERNEMQ_KUBERNETES_NAMESPACE}.svc.${DOCKER_VERNEMQ_KUBERNETES_CLUSTER_NAME} - fi - - sed -i.bak -r "s/VerneMQ@.+/VerneMQ@${VERNEMQ_KUBERNETES_HOSTNAME}/" ${VERNEMQ_VM_ARGS_FILE} - # Hack into K8S DNS resolution (temporarily) - kube_pod_names=$(echo ${podList} | jq '.items[].spec.hostname' | sed 's/"//g' | tr '\n' ' ' | sed 's/ *$//') - - for kube_pod_name in $kube_pod_names; do - if [[ $kube_pod_name == "null" ]]; then - echo "Kubernetes discovery selected, but no pods found. Maybe we're the first?" - echo "Anyway, we won't attempt to join any cluster." - break - fi - if [[ $kube_pod_name != $MY_POD_NAME ]]; then - discoveryHostname="${kube_pod_name}.${VERNEMQ_KUBERNETES_SUBDOMAIN}.${DOCKER_VERNEMQ_KUBERNETES_NAMESPACE}.svc.${DOCKER_VERNEMQ_KUBERNETES_CLUSTER_NAME}" - start_join_cluster=1 - echo "Will join an existing Kubernetes cluster with discovery node at ${discoveryHostname}" - echo "-eval \"vmq_server_cmd:node_join('VerneMQ@${discoveryHostname}')\"" >> ${VERNEMQ_VM_ARGS_FILE} - echo "Did I previously leave the cluster? If so, purging old state." - curl -fsSL http://${discoveryHostname}:8888/status.json >/dev/null 2>&1 || - (echo "Can't download status.json, better to exit now" && exit 1) - curl -fsSL http://${discoveryHostname}:8888/status.json | grep -q ${VERNEMQ_KUBERNETES_HOSTNAME} || - (echo "Cluster doesn't know about me, this means I've left previously. Purging old state..." && rm -rf /vernemq/data/*) - break - fi - done -fi - -if [ -f "${VERNEMQ_CONF_LOCAL_FILE}" ]; then - cp "${VERNEMQ_CONF_LOCAL_FILE}" ${VERNEMQ_CONF_FILE} - sed -i -r "s/###IPADDRESS###/${IP_ADDRESS}/" ${VERNEMQ_CONF_FILE} -else - sed -i '/########## Start ##########/,/########## End ##########/d' ${VERNEMQ_CONF_FILE} - - echo "########## Start ##########" >> ${VERNEMQ_CONF_FILE} - - env | grep DOCKER_VERNEMQ | grep -v 'DISCOVERY_NODE\|KUBERNETES\|SWARM\|COMPOSE\|DOCKER_VERNEMQ_USER' | cut -c 16- | awk '{match($0,/^[A-Z0-9_]*/)}{print tolower(substr($0,RSTART,RLENGTH)) substr($0,RLENGTH+1)}' | sed 's/__/./g' >> ${VERNEMQ_CONF_FILE} - - users_are_set=$(env | grep DOCKER_VERNEMQ_USER) - if [ ! -z "$users_are_set" ]; then - echo "vmq_passwd.password_file = /vernemq/etc/vmq.passwd" >> ${VERNEMQ_CONF_FILE} - touch /vernemq/etc/vmq.passwd - fi - - for vernemq_user in $(env | grep DOCKER_VERNEMQ_USER); do - username=$(echo $vernemq_user | awk -F '=' '{ print $1 }' | sed 's/DOCKER_VERNEMQ_USER_//g' | tr '[:upper:]' '[:lower:]') - password=$(echo $vernemq_user | awk -F '=' '{ print $2 }') - /vernemq/bin/vmq-passwd /vernemq/etc/vmq.passwd $username <> ${VERNEMQ_CONF_FILE} - fi - - if [ -z "$DOCKER_VERNEMQ_ERLANG__DISTRIBUTION__PORT_RANGE__MAXIMUM" ]; then - echo "erlang.distribution.port_range.maximum = 9109" >> ${VERNEMQ_CONF_FILE} - fi - - if [ -z "$DOCKER_VERNEMQ_LISTENER__TCP__DEFAULT" ]; then - echo "listener.tcp.default = ${IP_ADDRESS}:1883" >> ${VERNEMQ_CONF_FILE} - fi - - if [ -z "$DOCKER_VERNEMQ_LISTENER__WS__DEFAULT" ]; then - echo "listener.ws.default = ${IP_ADDRESS}:8080" >> ${VERNEMQ_CONF_FILE} - fi - - if [ -z "$DOCKER_VERNEMQ_LISTENER__VMQ__CLUSTERING" ]; then - echo "listener.vmq.clustering = ${IP_ADDRESS}:44053" >> ${VERNEMQ_CONF_FILE} - fi - - if [ -z "$DOCKER_VERNEMQ_LISTENER__HTTP__METRICS" ]; then - echo "listener.http.metrics = ${IP_ADDRESS}:8888" >> ${VERNEMQ_CONF_FILE} - fi - - echo "########## End ##########" >> ${VERNEMQ_CONF_FILE} -fi - -if [ ! -z "$DOCKER_VERNEMQ_ERLANG__MAX_PORTS" ]; then - sed -i.bak -r "s/\+Q.+/\+Q ${DOCKER_VERNEMQ_ERLANG__MAX_PORTS}/" ${VERNEMQ_VM_ARGS_FILE} -fi - -if [ ! -z "$DOCKER_VERNEMQ_ERLANG__PROCESS_LIMIT" ]; then - sed -i.bak -r "s/\+P.+/\+P ${DOCKER_VERNEMQ_ERLANG__PROCESS_LIMIT}/" ${VERNEMQ_VM_ARGS_FILE} -fi - -if [ ! -z "$DOCKER_VERNEMQ_ERLANG__MAX_ETS_TABLES" ]; then - sed -i.bak -r "s/\+e.+/\+e ${DOCKER_VERNEMQ_ERLANG__MAX_ETS_TABLES}/" ${VERNEMQ_VM_ARGS_FILE} -fi - -if [ ! -z "$DOCKER_VERNEMQ_ERLANG__DISTRIBUTION_BUFFER_SIZE" ]; then - sed -i.bak -r "s/\+zdbbl.+/\+zdbbl ${DOCKER_VERNEMQ_ERLANG__DISTRIBUTION_BUFFER_SIZE}/" ${VERNEMQ_VM_ARGS_FILE} -fi - -# Check configuration file -/vernemq/bin/vernemq config generate 2>&1 > /dev/null | tee /tmp/config.out | grep error - -if [ $? -ne 1 ]; then - echo "configuration error, exit" - echo "$(cat /tmp/config.out)" - exit $? -fi - -pid=0 - -# SIGUSR1-handler -siguser1_handler() { - echo "stopped" -} - -# SIGTERM-handler -sigterm_handler() { - if [ $pid -ne 0 ]; then - if [ -d "${SECRETS_KUBERNETES_DIR}" ] ; then - # this will stop the VerneMQ process, but first drain the node from all existing client sessions (-k) - if [ -n "$VERNEMQ_KUBERNETES_HOSTNAME" ]; then - terminating_node_name=VerneMQ@$VERNEMQ_KUBERNETES_HOSTNAME - else - terminating_node_name=VerneMQ@$IP_ADDRESS - fi - podList=$(k8sCurlGet "api/v1/namespaces/${DOCKER_VERNEMQ_KUBERNETES_NAMESPACE}/pods?labelSelector=${DOCKER_VERNEMQ_KUBERNETES_LABEL_SELECTOR}") - kube_pod_names=$(echo ${podList} | jq '.items[].spec.hostname' | sed 's/"//g' | tr '\n' ' ' | sed 's/ *$//') - if [ "$kube_pod_names" = "$MY_POD_NAME" ]; then - echo "I'm the only pod remaining. Not performing leave and/or state purge." - /vernemq/bin/vmq-admin node stop >/dev/null - else - # https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#read-pod-v1-core - podResponse=$(k8sCurlGet api/v1/namespaces/${DOCKER_VERNEMQ_KUBERNETES_NAMESPACE}/pods/$(hostname) ) - statefulSetName=$(echo ${podResponse} | jq -r '.metadata.ownerReferences[0].name') - - # https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#-strong-read-operations-statefulset-v1-apps-strong- - statefulSetResponse=$(k8sCurlGet "apis/apps/v1/namespaces/${DOCKER_VERNEMQ_KUBERNETES_NAMESPACE}/statefulsets/${statefulSetName}" ) - - isCodeForbidden=$(echo ${statefulSetResponse} | jq '.code == 403') - if [[ ${isCodeForbidden} == "true" ]]; then - echo "Permission error: Cannot access URL ${statefulSetPath}: $(echo ${statefulSetResponse} | jq '.reason,.code,.message')" - fi - - reschedule=$(echo ${statefulSetResponse} | jq '.status.replicas == .status.readyReplicas') - scaled_down=$(echo ${statefulSetResponse} | jq '.status.currentReplicas == .status.updatedReplicas') - - if [[ $reschedule == "true" ]]; then - # Perhaps is an scale down? - if [[ $scaled_down == "true" ]]; then - echo "Seems that this is a scale down scenario. Leaving cluster." - /vernemq/bin/vmq-admin cluster leave node=${terminating_node_name} -k && rm -rf /vernemq/data/* - else - echo "Reschedule is true. Not leaving the cluster." - /vernemq/bin/vmq-admin node stop >/dev/null - fi - else - echo "Reschedule is false. Leaving the cluster." - /vernemq/bin/vmq-admin cluster leave node=${terminating_node_name} -k && rm -rf /vernemq/data/* - fi - fi - else - if [ -n "$DOCKER_VERNEMQ_SWARM" ]; then - terminating_node_name=VerneMQ@$(hostname -i) - # For Swarm we keep the old "cluster leave" approach for now - echo "Swarm node is leaving the cluster." - /vernemq/bin/vmq-admin cluster leave node=${terminating_node_name} -k && rm -rf /vernemq/data/* - else - # In non-k8s mode: Stop the vernemq node gracefully - /vernemq/bin/vmq-admin node stop >/dev/null - fi - fi - kill -s TERM ${pid} - WAITFOR_PID=${pid} - pid=0 - wait ${WAITFOR_PID} - fi - exit 143; # 128 + 15 -- SIGTERM -} - -if [ ! -s ${VERNEMQ_VM_ARGS_FILE} ]; then - echo "ls -l ${VERNEMQ_ETC_DIR}" - ls -l ${VERNEMQ_ETC_DIR} - echo "###" >&2 - echo "### Configuration file ${VERNEMQ_VM_ARGS_FILE} is empty! This will not work." >&2 - echo "### Exiting now." >&2 - echo "###" >&2 - exit 1 -fi - -# Setup OS signal handlers -trap 'siguser1_handler' SIGUSR1 -trap 'sigterm_handler' SIGTERM - -# Start VerneMQ -/vernemq/bin/vernemq console -noshell -noinput $@ & -pid=$! -if [ $start_join_cluster -eq 1 ]; then - mkdir -p /var/log/vernemq/log - join_cluster > /var/log/vernemq/log/join_cluster.log & -fi -if [ -n "$API_KEY" ]; then - sleep 10 && echo "Adding API_KEY..." && /vernemq/bin/vmq-admin api-key add key="${API_KEY:-DEFAULT}" - vmq-admin api-key show -fi -wait $pid diff --git a/docker/vernemq/files/vm.args b/docker/vernemq/files/vm.args deleted file mode 100644 index afb3c022bb..0000000000 --- a/docker/vernemq/files/vm.args +++ /dev/null @@ -1,15 +0,0 @@ -+P 512000 -+e 256000 --env ERL_CRASH_DUMP /erl_crash.dump --env ERL_FULLSWEEP_AFTER 0 -+Q 512000 -+A 64 --setcookie vmq --name VerneMQ@127.0.0.1 -+K true -+W w -+sbwt none -+sbwtdcpu none -+sbwtdio none --smp enable -+zdbbl 32768 From 246554fe21dfba5ee0d94afb1d6ecd2ce81bbd35 Mon Sep 17 00:00:00 2001 From: Rodney Osodo Date: Mon, 10 Feb 2025 14:06:36 +0300 Subject: [PATCH 03/13] feat: MQTT Target username and password for authenticated remotes Signed-off-by: Rodney Osodo --- cmd/mqtt/main.go | 32 ++++++++++++++++++++++++++++++-- pkg/messaging/mqtt/publisher.go | 4 ++-- pkg/messaging/mqtt/pubsub.go | 15 +++++++++------ pkg/messaging/mqtt/setup_test.go | 2 +- 4 files changed, 42 insertions(+), 11 deletions(-) diff --git a/cmd/mqtt/main.go b/cmd/mqtt/main.go index 32bd0353c7..e775a18547 100644 --- a/cmd/mqtt/main.go +++ b/cmd/mqtt/main.go @@ -39,6 +39,7 @@ import ( "github.com/absmach/supermq/pkg/uuid" "github.com/caarlos0/env/v11" "github.com/cenkalti/backoff/v4" + "github.com/eclipse/paho.mqtt.golang/packets" "golang.org/x/sync/errgroup" ) @@ -54,6 +55,8 @@ type config struct { MQTTPort string `env:"SMQ_MQTT_ADAPTER_MQTT_PORT" envDefault:"1883"` MQTTTargetHost string `env:"SMQ_MQTT_ADAPTER_MQTT_TARGET_HOST" envDefault:"localhost"` MQTTTargetPort string `env:"SMQ_MQTT_ADAPTER_MQTT_TARGET_PORT" envDefault:"1883"` + MQTTTargetUsername string `env:"SMQ_MQTT_ADAPTER_MQTT_TARGET_USERNAME" envDefault:""` + MQTTTargetPassword string `env:"SMQ_MQTT_ADAPTER_MQTT_TARGET_PASSWORD" envDefault:""` MQTTForwarderTimeout time.Duration `env:"SMQ_MQTT_ADAPTER_FORWARDER_TIMEOUT" envDefault:"30s"` MQTTTargetHealthCheck string `env:"SMQ_MQTT_ADAPTER_MQTT_TARGET_HEALTH_CHECK" envDefault:""` MQTTQoS uint8 `env:"SMQ_MQTT_ADAPTER_MQTT_QOS" envDefault:"1"` @@ -135,7 +138,7 @@ func main() { defer bsub.Close() bsub = brokerstracing.NewPubSub(serverConfig, tracer, bsub) - mpub, err := mqttpub.NewPublisher(fmt.Sprintf("mqtt://%s:%s", cfg.MQTTTargetHost, cfg.MQTTTargetPort), cfg.MQTTQoS, cfg.MQTTForwarderTimeout) + mpub, err := mqttpub.NewPublisher(fmt.Sprintf("mqtt://%s:%s", cfg.MQTTTargetHost, cfg.MQTTTargetPort), cfg.MQTTTargetUsername, cfg.MQTTTargetPassword, cfg.MQTTQoS, cfg.MQTTForwarderTimeout) if err != nil { logger.Error(fmt.Sprintf("failed to create MQTT publisher: %s", err)) exitCode = 1 @@ -222,7 +225,10 @@ func main() { go chc.CallHome(ctx) } - var interceptor session.Interceptor + var interceptor = interceptor{ + username: cfg.MQTTTargetUsername, + password: cfg.MQTTTargetPassword, + } logger.Info(fmt.Sprintf("Starting MQTT proxy on port %s", cfg.MQTTPort)) g.Go(func() error { return proxyMQTT(ctx, cfg, logger, h, interceptor) @@ -318,3 +324,25 @@ func stopSignalHandler(ctx context.Context, cancel context.CancelFunc, logger *s return nil } } + +type interceptor struct { + username string + password string +} + +func (ic interceptor) Intercept(ctx context.Context, pkt packets.ControlPacket, dir session.Direction) (packets.ControlPacket, error) { + if connectPkt, ok := pkt.(*packets.ConnectPacket); ok { + if ic.username != "" { + connectPkt.Username = ic.username + connectPkt.UsernameFlag = true + } + if ic.password != "" { + connectPkt.Password = []byte(ic.password) + connectPkt.PasswordFlag = true + } + + return connectPkt, nil + } + + return pkt, nil +} diff --git a/pkg/messaging/mqtt/publisher.go b/pkg/messaging/mqtt/publisher.go index 54364c31de..ad51a04b89 100644 --- a/pkg/messaging/mqtt/publisher.go +++ b/pkg/messaging/mqtt/publisher.go @@ -23,8 +23,8 @@ type publisher struct { } // NewPublisher returns a new MQTT message publisher. -func NewPublisher(address string, qos uint8, timeout time.Duration) (messaging.Publisher, error) { - client, err := newClient(address, "mqtt-publisher", timeout) +func NewPublisher(address, username, password string, qos uint8, timeout time.Duration) (messaging.Publisher, error) { + client, err := newClient(address, username, password, "mqtt-publisher", timeout) if err != nil { return nil, err } diff --git a/pkg/messaging/mqtt/pubsub.go b/pkg/messaging/mqtt/pubsub.go index 37c81a122d..7520673892 100644 --- a/pkg/messaging/mqtt/pubsub.go +++ b/pkg/messaging/mqtt/pubsub.go @@ -16,8 +16,6 @@ import ( "google.golang.org/protobuf/proto" ) -const username = "supermq-mqtt" - var ( // ErrConnect indicates that connection to MQTT broker failed. ErrConnect = errors.New("failed to connect to MQTT broker") @@ -54,13 +52,15 @@ type pubsub struct { logger *slog.Logger mu sync.RWMutex address string + username string + password string timeout time.Duration subscriptions map[string]subscription } // NewPubSub returns MQTT message publisher/subscriber. -func NewPubSub(url string, qos uint8, timeout time.Duration, logger *slog.Logger) (messaging.PubSub, error) { - client, err := newClient(url, "mqtt-publisher", timeout) +func NewPubSub(url, username, password string, qos uint8, timeout time.Duration, logger *slog.Logger) (messaging.PubSub, error) { + client, err := newClient(url, username, password, "mqtt-publisher", timeout) if err != nil { return nil, err } @@ -71,6 +71,8 @@ func NewPubSub(url string, qos uint8, timeout time.Duration, logger *slog.Logger qos: qos, }, address: url, + username: username, + password: password, timeout: timeout, logger: logger, subscriptions: make(map[string]subscription), @@ -98,7 +100,7 @@ func (ps *pubsub) Subscribe(ctx context.Context, cfg messaging.SubscriberConfig) } } default: - client, err := newClient(ps.address, cfg.ID, ps.timeout) + client, err := newClient(ps.address, ps.username, ps.password, cfg.ID, ps.timeout) if err != nil { return err } @@ -169,9 +171,10 @@ func (s *subscription) unsubscribe(topic string, timeout time.Duration) error { return token.Error() } -func newClient(address, id string, timeout time.Duration) (mqtt.Client, error) { +func newClient(address, username, password, id string, timeout time.Duration) (mqtt.Client, error) { opts := mqtt.NewClientOptions(). SetUsername(username). + SetPassword(password). AddBroker(address). SetClientID(id) client := mqtt.NewClient(opts) diff --git a/pkg/messaging/mqtt/setup_test.go b/pkg/messaging/mqtt/setup_test.go index 9a01af691b..4a90012e01 100644 --- a/pkg/messaging/mqtt/setup_test.go +++ b/pkg/messaging/mqtt/setup_test.go @@ -63,7 +63,7 @@ func TestMain(m *testing.M) { } if err := pool.Retry(func() error { - pubsub, err = mqttpubsub.NewPubSub(address, 2, brokerTimeout, logger) + pubsub, err = mqttpubsub.NewPubSub(address, "supermq", "", 2, brokerTimeout, logger) return err }); err != nil { log.Fatalf("Could not connect to docker: %s", err) From b0652b04ec7bc758789203121c1a9bd5184b8701 Mon Sep 17 00:00:00 2001 From: Rodney Osodo Date: Mon, 10 Feb 2025 14:09:33 +0300 Subject: [PATCH 04/13] chore: upgrade go modules Signed-off-by: Rodney Osodo --- .github/workflows/api-tests.yml | 2 +- .github/workflows/build.yml | 2 +- .github/workflows/check-generated-files.yml | 2 +- .github/workflows/tests.yml | 4 +- docker/.env | 4 +- go.mod | 37 +++++----- go.sum | 76 ++++++++++----------- 7 files changed, 62 insertions(+), 65 deletions(-) diff --git a/.github/workflows/api-tests.yml b/.github/workflows/api-tests.yml index b68ded91ab..cf999d40b2 100644 --- a/.github/workflows/api-tests.yml +++ b/.github/workflows/api-tests.yml @@ -46,7 +46,7 @@ jobs: - name: Install Go uses: actions/setup-go@v5 with: - go-version: 1.22.x + go-version: 1.23.x cache-dependency-path: "go.sum" - name: Build images diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b60a6ec4ea..b33514f887 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -24,7 +24,7 @@ jobs: - name: Install Go uses: actions/setup-go@v5 with: - go-version: 1.22.x + go-version: 1.23.x cache-dependency-path: "go.sum" - name: Run tests diff --git a/.github/workflows/check-generated-files.yml b/.github/workflows/check-generated-files.yml index a2998b657f..caf9ab5ce9 100644 --- a/.github/workflows/check-generated-files.yml +++ b/.github/workflows/check-generated-files.yml @@ -21,7 +21,7 @@ jobs: - name: Install Go uses: actions/setup-go@v5 with: - go-version: 1.22.x + go-version: 1.23.x cache-dependency-path: "go.sum" - name: Check for changes in go.mod diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 2cd96682f9..fe0049e673 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -20,7 +20,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v5 with: - go-version: 1.22.x + go-version: 1.23.x cache-dependency-path: "go.sum" - name: Install protolint @@ -63,7 +63,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v5 with: - go-version: 1.22.x + go-version: 1.23.x cache-dependency-path: "go.sum" - name: Check for changes in specific paths diff --git a/docker/.env b/docker/.env index 95732c2539..973a98a348 100644 --- a/docker/.env +++ b/docker/.env @@ -290,7 +290,7 @@ SMQ_CLIENTS_INSTANCE_ID= #### Clients Client Config SMQ_CLIENTS_URL=http://clients:9006 SMQ_CLIENTS_AUTH_GRPC_URL=clients:7006 -SMQ_CLIENTS_AUTH_GRPC_TIMEOUT=1s +SMQ_CLIENTS_AUTH_GRPC_TIMEOUT=300s SMQ_CLIENTS_AUTH_GRPC_CLIENT_CERT=${GRPC_MTLS:+./ssl/certs/clients-grpc-client.crt} SMQ_CLIENTS_AUTH_GRPC_CLIENT_KEY=${GRPC_MTLS:+./ssl/certs/clients-grpc-client.key} SMQ_CLIENTS_AUTH_GRPC_CLIENT_CA_CERTS=${GRPC_MTLS:+./ssl/certs/ca.crt} @@ -318,7 +318,7 @@ SMQ_CHANNELS_INSTANCE_ID= #### Channels Client Config SMQ_CHANNELS_URL=http://channels:9005 SMQ_CHANNELS_GRPC_URL=channels:7005 -SMQ_CHANNELS_GRPC_TIMEOUT=1s +SMQ_CHANNELS_GRPC_TIMEOUT=300s SMQ_CHANNELS_GRPC_CLIENT_CERT=${GRPC_MTLS:+./ssl/certs/channels-grpc-client.crt} SMQ_CHANNELS_GRPC_CLIENT_KEY=${GRPC_MTLS:+./ssl/certs/channels-grpc-client.key} SMQ_CHANNELS_GRPC_CLIENT_CA_CERTS=${GRPC_MTLS:+./ssl/certs/ca.crt} diff --git a/go.mod b/go.mod index 57bf5c39fb..53a0ae7bad 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.23.4 require ( github.com/0x6flab/namegenerator v1.4.0 github.com/absmach/callhome v0.14.0 - github.com/absmach/certs v0.0.0-20241014135535-3f118b801054 + github.com/absmach/certs v0.0.0-20250127084046-fb0da0712b2b github.com/absmach/mgate v0.4.5 github.com/absmach/senml v1.0.6 github.com/authzed/authzed-go v1.3.0 @@ -52,7 +52,7 @@ require ( golang.org/x/crypto v0.33.0 golang.org/x/oauth2 v0.26.0 golang.org/x/sync v0.11.0 - google.golang.org/genproto/googleapis/rpc v0.0.0-20250127172529-29210b9bc287 + google.golang.org/genproto/googleapis/rpc v0.0.0-20250207221924-e9438ea467c6 google.golang.org/grpc v1.70.0 google.golang.org/protobuf v1.36.5 gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df @@ -65,7 +65,7 @@ require ( github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect - github.com/antlr4-go/antlr/v4 v4.13.0 // indirect + github.com/antlr4-go/antlr/v4 v4.13.1 // indirect github.com/authzed/cel-go v0.20.2 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/ccoveille/go-safecast v1.5.0 // indirect @@ -91,36 +91,35 @@ require ( github.com/go-logfmt/logfmt v0.6.0 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/goccy/go-json v0.10.3 // indirect + github.com/goccy/go-json v0.10.5 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/gopherjs/gopherjs v1.17.2 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-retryablehttp v0.7.7 // indirect github.com/hashicorp/go-rootcerts v1.0.2 // indirect - github.com/hashicorp/go-secure-stdlib/parseutil v0.1.8 // indirect + github.com/hashicorp/go-secure-stdlib/parseutil v0.1.9 // indirect github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect - github.com/hashicorp/go-sockaddr v1.0.6 // indirect + github.com/hashicorp/go-sockaddr v1.0.7 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jackc/pgio v1.0.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect - github.com/jackc/pgx/v4 v4.18.3 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/jtolds/gls v4.20.0+incompatible // indirect github.com/jzelinskie/stringz v0.0.3 // indirect - github.com/klauspost/compress v1.17.9 // indirect + github.com/klauspost/compress v1.17.11 // indirect github.com/lestrrat-go/blackmagic v1.0.2 // indirect github.com/lestrrat-go/httpcc v1.0.1 // indirect github.com/lestrrat-go/httprc v1.0.6 // indirect github.com/lestrrat-go/iter v1.0.2 // indirect github.com/lestrrat-go/option v1.0.1 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect @@ -131,21 +130,21 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // indirect github.com/opencontainers/runc v1.1.14 // indirect - github.com/pion/dtls/v3 v3.0.2 // indirect - github.com/pion/logging v0.2.2 // indirect + github.com/pion/dtls/v3 v3.0.4 // indirect + github.com/pion/logging v0.2.3 // indirect github.com/pion/transport/v3 v3.0.7 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/planetscale/vtprotobuf v0.6.1-0.20240917153116-6f2963f01587 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_model v0.6.1 // indirect - github.com/prometheus/common v0.61.0 // indirect + github.com/prometheus/common v0.62.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect github.com/rs/zerolog v1.33.0 // indirect github.com/ryanuber/go-glob v1.0.0 // indirect - github.com/samber/lo v1.49.0 // indirect + github.com/samber/lo v1.49.1 // indirect github.com/segmentio/asm v1.2.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect - github.com/smarty/assertions v1.15.0 // indirect + github.com/smarty/assertions v1.16.0 // indirect github.com/spf13/pflag v1.0.6 // indirect github.com/stoewer/go-strcase v1.3.0 // indirect github.com/stretchr/objx v0.5.2 // indirect @@ -157,14 +156,14 @@ require ( go.opentelemetry.io/otel/metric v1.34.0 // indirect go.opentelemetry.io/proto/otlp v1.5.0 // indirect go.uber.org/atomic v1.11.0 // indirect - golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect + golang.org/x/exp v0.0.0-20250207012021-f9890c6ad9f3 // indirect golang.org/x/net v0.34.0 // indirect golang.org/x/sys v0.30.0 // indirect golang.org/x/text v0.22.0 // indirect - golang.org/x/time v0.9.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250127172529-29210b9bc287 // indirect + golang.org/x/time v0.10.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250207221924-e9438ea467c6 // indirect gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect + k8s.io/utils v0.0.0-20241210054802-24370beab758 // indirect ) diff --git a/go.sum b/go.sum index 00582f075e..b39e5cf59e 100644 --- a/go.sum +++ b/go.sum @@ -19,14 +19,14 @@ github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrd github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= github.com/absmach/callhome v0.14.0 h1:zB4tIZJ1YUmZ1VGHFPfMA/Lo6/Mv19y2dvoOiXj2BWs= github.com/absmach/callhome v0.14.0/go.mod h1:l12UJOfibK4Muvg/AbupHuquNV9qSz/ROdTEPg7f2Vk= -github.com/absmach/certs v0.0.0-20241014135535-3f118b801054 h1:NsIwp+ueKxDx8XftruA4hz8WUgyWq7eBE344nJt0LJg= -github.com/absmach/certs v0.0.0-20241014135535-3f118b801054/go.mod h1:bEAb/HjPztlrMmz8dLeJTke4Tzu9yW3+hY5eldEUtSY= +github.com/absmach/certs v0.0.0-20250127084046-fb0da0712b2b h1:EGIqL1bARjRSS7kH98Q5O/g7lZN/Q0KtAVX5mxRcq84= +github.com/absmach/certs v0.0.0-20250127084046-fb0da0712b2b/go.mod h1:g6Kqge7RVxwt+LRxqt+09cqa2SgPAwXvIPoyPsEqZlQ= github.com/absmach/mgate v0.4.5 h1:l6RmrEsR9jxkdb9WHUSecmT0HA41TkZZQVffFfUAIfI= github.com/absmach/mgate v0.4.5/go.mod h1:IvRIHZexZPEIAPmmaJF0L5DY2ERjj+GxRGitOW4s6qo= github.com/absmach/senml v1.0.6 h1:WPeIl6vQ00k7ghWSZYT/QP0KUxq2+4zQoaC7240pLFk= github.com/absmach/senml v1.0.6/go.mod h1:QnJNPy1DJPy0+qUW21PTcH/xoh0LgfYZxTfwriMIvmQ= -github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI= -github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g= +github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= +github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= github.com/authzed/authzed-go v1.3.0 h1:jKIMpYDy+6WoOwl32HRURxLZxNGm+I7ObUlTntEPcXA= github.com/authzed/authzed-go v1.3.0/go.mod h1:MYkXImtFAxrM/bVZvmC/WO+gZC9RLlvpCM51SLaUZb0= github.com/authzed/cel-go v0.20.2 h1:GlmLecGry7Z8HU0k+hmaHHUV05ZHrsFxduXHtIePvck= @@ -99,8 +99,6 @@ github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2 github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= -github.com/go-chi/chi v1.5.5 h1:vOB/HbEMt9QqBqErz07QehcOKHaWFtuj87tTDVz2qXE= -github.com/go-chi/chi v1.5.5/go.mod h1:C9JqLr3tIYjDOZpzn+BCuxY8z8vmca43EeMgyZt7irw= github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8= github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk= @@ -127,8 +125,8 @@ github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqw github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-test/deep v1.0.2 h1:onZX1rnHT3Wv6cqNgYyFOOlgVKJrksuCMCRvJStbMYw= github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= -github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= -github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= +github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gofrs/uuid/v5 v5.3.0 h1:m0mUMr+oVYUdxpMLgSYCZiXe7PuVPnI94+OMeVBNedk= @@ -158,8 +156,8 @@ github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aN github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.0 h1:VD1gqscl4nYs1YxVuSdemTrSgTKrwOWDK0FVFMqm+Cg= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.0/go.mod h1:4EgsQoS4TOhJizV+JTFg40qx1Ofh3XmXEQNBpgvNT40= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 h1:e9Rjr40Z98/clHv5Yg79Is0NtosR5LXRvdr7o/6NwbA= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1/go.mod h1:tIxuGz/9mpox++sgp9fJjHO0+q1X9/UOWd798aAm22M= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -173,12 +171,12 @@ github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISH github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= -github.com/hashicorp/go-secure-stdlib/parseutil v0.1.8 h1:iBt4Ew4XEGLfh6/bPk4rSYmuZJGizr6/x/AEizP0CQc= -github.com/hashicorp/go-secure-stdlib/parseutil v0.1.8/go.mod h1:aiJI+PIApBRQG7FZTEBx5GiiX+HbOHilUdNxUZi4eV0= +github.com/hashicorp/go-secure-stdlib/parseutil v0.1.9 h1:FW0YttEnUNDJ2WL9XcrrfteS1xW8u+sh4ggM8pN5isQ= +github.com/hashicorp/go-secure-stdlib/parseutil v0.1.9/go.mod h1:Ll013mhdmsVDuoIXVfBtvgGJsXDYkTw1kooNcoCXuE0= github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts= github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4= -github.com/hashicorp/go-sockaddr v1.0.6 h1:RSG8rKU28VTUTvEKghe5gIhIQpv8evvNpnDEyqO4u9I= -github.com/hashicorp/go-sockaddr v1.0.6/go.mod h1:uoUUmtwU7n9Dv3O4SNLeFvg0SxQ3lyjsj6+CCykpaxI= +github.com/hashicorp/go-sockaddr v1.0.7 h1:G+pTkSO01HpR5qCxg7lxfsFEZaG+C0VssTy/9dbT+Fw= +github.com/hashicorp/go-sockaddr v1.0.7/go.mod h1:FZQbEYa1pxkQ7WLpyXJ6cbjpT8q0YgQaK/JakXqGyWw= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/vault/api v1.15.0 h1:O24FYQCWwhwKnF7CuSqP30S51rTV7vz1iACXE/pj5DA= @@ -234,9 +232,8 @@ github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08 github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= +github.com/jackc/pgx/v4 v4.18.2 h1:xVpYkNR5pk5bMCZGfClbO962UIqVABcAGt7ha1s/FeU= github.com/jackc/pgx/v4 v4.18.2/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw= -github.com/jackc/pgx/v4 v4.18.3 h1:dE2/TrEsGX3RBprb3qryqSV9Y60iZN1C6i8IrmW9/BA= -github.com/jackc/pgx/v4 v4.18.3/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw= github.com/jackc/pgx/v5 v5.7.2 h1:mLoDLV6sonKlvjIEsV56SkWNCnuNv531l94GaIzO+XI= github.com/jackc/pgx/v5 v5.7.2/go.mod h1:ncY89UGWxg82EykZUwSpUKEfccBGGYq1xjrOpsbsfGQ= github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= @@ -253,8 +250,8 @@ github.com/jzelinskie/stringz v0.0.3 h1:0GhG3lVMYrYtIvRbxvQI6zqRTT1P1xyQlpa0FhfU github.com/jzelinskie/stringz v0.0.3/go.mod h1:hHYbgxJuNLRw91CmpuFsYEOyQqpDVFg8pvEh23vy4P0= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= -github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= +github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -287,8 +284,9 @@ github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= @@ -328,10 +326,10 @@ github.com/ory/dockertest/v3 v3.11.0/go.mod h1:VIPxS1gwT9NpPOrfD3rACs8Y9Z7yhzO4S github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/pion/dtls/v3 v3.0.2 h1:425DEeJ/jfuTTghhUDW0GtYZYIwwMtnKKJNMcWccTX0= -github.com/pion/dtls/v3 v3.0.2/go.mod h1:dfIXcFkKoujDQ+jtd8M6RgqKK3DuaUilm3YatAbGp5k= -github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY= -github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms= +github.com/pion/dtls/v3 v3.0.4 h1:44CZekewMzfrn9pmGrj5BNnTMDCFwr+6sLH+cCuLM7U= +github.com/pion/dtls/v3 v3.0.4/go.mod h1:R373CsjxWqNPf6MEkfdy3aSe9niZvL/JaKlGeFphtMg= +github.com/pion/logging v0.2.3 h1:gHuf0zpoh1GW67Nr6Gj4cv5Z9ZscU7g/EaoC/Ke/igI= +github.com/pion/logging v0.2.3/go.mod h1:z8YfknkquMe1csOrxK5kc+5/ZPAzMxbKLX5aXpbpC90= github.com/pion/transport/v3 v3.0.7 h1:iRbMH05BzSNwhILHoBoAPxoB9xQgOaJk+591KC9P1o0= github.com/pion/transport/v3 v3.0.7/go.mod h1:YleKiTZ4vqNxVwh77Z0zytYi7rXHl7j6uPLGhhz9rwo= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -351,8 +349,8 @@ github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/j github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= -github.com/prometheus/common v0.61.0 h1:3gv/GThfX0cV2lpO7gkTUwZru38mxevy90Bj8YFSRQQ= -github.com/prometheus/common v0.61.0/go.mod h1:zr29OCN/2BsJRaFwG8QOBr41D6kkchKbpeNH7pAjb/s= +github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= +github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/rabbitmq/amqp091-go v1.10.0 h1:STpn5XsHlHGcecLmMFCtg7mqq0RnD+zFr4uzukfVhBw= @@ -373,8 +371,8 @@ github.com/rubenv/sql-migrate v1.7.1/go.mod h1:Ob2Psprc0/3ggbM6wCzyYVFFuc6FyZrb2 github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= -github.com/samber/lo v1.49.0 h1:AGnTnQrg1jpFuwECPUSoxZCfVH5W22b605kWSry3YxM= -github.com/samber/lo v1.49.0/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o= +github.com/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew= +github.com/samber/lo v1.49.1/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= @@ -384,8 +382,8 @@ github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMB github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/smarty/assertions v1.15.0 h1:cR//PqUBUiQRakZWqBiFFQ9wb8emQGDb0HeGdqGByCY= -github.com/smarty/assertions v1.15.0/go.mod h1:yABtdzeQs6l1brC900WlRNwj6ZR55d7B+E8C6HtKdec= +github.com/smarty/assertions v1.16.0 h1:EvHNkdRA4QHMrn75NZSoUQ/mAUXAYWfatfB01yTCzfY= +github.com/smarty/assertions v1.16.0/go.mod h1:duaaFdCS0K9dnoM50iyek/eYINOZ64gbh1Xlf6LG7AI= github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY= github.com/smartystreets/goconvey v1.8.1/go.mod h1:+/u4qLyY6x1jReYOp7GOM2FSt8aP9CzCZL03bI28W60= github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= @@ -486,8 +484,8 @@ golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZP golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk= -golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY= +golang.org/x/exp v0.0.0-20250207012021-f9890c6ad9f3 h1:qNgPs5exUA+G0C96DrPwNrvLSj7GT/9D+3WMWUcUg34= +golang.org/x/exp v0.0.0-20250207012021-f9890c6ad9f3/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= @@ -570,8 +568,8 @@ golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= -golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= -golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4= +golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -600,10 +598,10 @@ google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto/googleapis/api v0.0.0-20250127172529-29210b9bc287 h1:A2ni10G3UlplFrWdCDJTl7D7mJ7GSRm37S+PDimaKRw= -google.golang.org/genproto/googleapis/api v0.0.0-20250127172529-29210b9bc287/go.mod h1:iYONQfRdizDB8JJBybql13nArx91jcUk7zCXEsOofM4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250127172529-29210b9bc287 h1:J1H9f+LEdWAfHcez/4cvaVBox7cOYT+IU6rgqj5x++8= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250127172529-29210b9bc287/go.mod h1:8BS3B93F/U1juMFq9+EDk+qOT5CO1R9IzXxG3PTqiRk= +google.golang.org/genproto/googleapis/api v0.0.0-20250207221924-e9438ea467c6 h1:L9JNMl/plZH9wmzQUHleO/ZZDSN+9Gh41wPczNy+5Fk= +google.golang.org/genproto/googleapis/api v0.0.0-20250207221924-e9438ea467c6/go.mod h1:iYONQfRdizDB8JJBybql13nArx91jcUk7zCXEsOofM4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250207221924-e9438ea467c6 h1:2duwAxN2+k0xLNpjnHTXoMUgnv6VPSp5fiqTuwSxjmI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250207221924-e9438ea467c6/go.mod h1:8BS3B93F/U1juMFq9+EDk+qOT5CO1R9IzXxG3PTqiRk= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= @@ -636,7 +634,7 @@ gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A= -k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/utils v0.0.0-20241210054802-24370beab758 h1:sdbE21q2nlQtFh65saZY+rRM6x6aJJI8IUa1AmH/qa0= +k8s.io/utils v0.0.0-20241210054802-24370beab758/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= moul.io/http2curl v1.0.0 h1:6XwpyZOYsgZJrU8exnG87ncVkU1FVCcTRpwzOkTDUi8= moul.io/http2curl v1.0.0/go.mod h1:f6cULg+e4Md/oW1cYmwW4IWQOVl2lGbmCNGOHvzX2kE= From 4012e611c8ef4fa8d80ed377360029ab08c8e3f3 Mon Sep 17 00:00:00 2001 From: Rodney Osodo Date: Mon, 10 Feb 2025 14:44:06 +0300 Subject: [PATCH 05/13] fix: remove rabbitmq config files from license check Signed-off-by: Rodney Osodo --- .github/workflows/check-license.yaml | 2 +- cmd/mqtt/main.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/check-license.yaml b/.github/workflows/check-license.yaml index 2977dd9b1e..3d2eb3380d 100644 --- a/.github/workflows/check-license.yaml +++ b/.github/workflows/check-license.yaml @@ -23,7 +23,7 @@ jobs: CHECK="" for file in $(grep -rl --exclude-dir={.git,build} \ --exclude=\*.{crt,key,pem,zed,hcl,md,json,csv,mod,sum,tmpl,args} \ - --exclude={CODEOWNERS,LICENSE,MAINTAINERS} \ + --exclude={CODEOWNERS,LICENSE,MAINTAINERS,enabled_plugins,rabbitmq.conf} \ .); do if ! head -n 5 "$file" | grep -q "Copyright (c) Abstract Machines"; then diff --git a/cmd/mqtt/main.go b/cmd/mqtt/main.go index e775a18547..9fccd4c2d9 100644 --- a/cmd/mqtt/main.go +++ b/cmd/mqtt/main.go @@ -225,7 +225,7 @@ func main() { go chc.CallHome(ctx) } - var interceptor = interceptor{ + interceptor := interceptor{ username: cfg.MQTTTargetUsername, password: cfg.MQTTTargetPassword, } From 27f7de56e32ce67c6d94479ba915c494f2648dce Mon Sep 17 00:00:00 2001 From: Rodney Osodo Date: Mon, 10 Feb 2025 22:39:49 +0300 Subject: [PATCH 06/13] test: add auth handler POC Signed-off-by: Rodney Osodo --- Makefile | 2 +- cmd/mqtt/main.go | 30 +------- cmd/rabbitmq-auth/main.go | 123 +++++++++++++++++++++++++++++++ docker/docker-compose.yml | 39 ++++++++++ docker/rabbitmq/enabled_plugins | 2 +- docker/rabbitmq/rabbitmq.conf | 12 ++- pkg/messaging/mqtt/publisher.go | 4 +- pkg/messaging/mqtt/pubsub.go | 18 ++--- pkg/messaging/mqtt/setup_test.go | 2 +- rabbitmq-auth/api/transport.go | 70 ++++++++++++++++++ 10 files changed, 257 insertions(+), 45 deletions(-) create mode 100644 cmd/rabbitmq-auth/main.go create mode 100644 rabbitmq-auth/api/transport.go diff --git a/Makefile b/Makefile index 5318a3c3ba..40dddcacee 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ SMQ_DOCKER_IMAGE_NAME_PREFIX ?= supermq BUILD_DIR ?= build -SERVICES = auth users clients groups channels domains http coap ws cli mqtt certs journal +SERVICES = auth users clients groups channels domains http coap ws cli mqtt certs journal rabbitmq-auth TEST_API_SERVICES = journal auth certs http clients users channels groups domains TEST_API = $(addprefix test_api_,$(TEST_API_SERVICES)) DOCKERS = $(addprefix docker_,$(SERVICES)) diff --git a/cmd/mqtt/main.go b/cmd/mqtt/main.go index 9fccd4c2d9..b961ad4aa2 100644 --- a/cmd/mqtt/main.go +++ b/cmd/mqtt/main.go @@ -39,7 +39,6 @@ import ( "github.com/absmach/supermq/pkg/uuid" "github.com/caarlos0/env/v11" "github.com/cenkalti/backoff/v4" - "github.com/eclipse/paho.mqtt.golang/packets" "golang.org/x/sync/errgroup" ) @@ -138,7 +137,7 @@ func main() { defer bsub.Close() bsub = brokerstracing.NewPubSub(serverConfig, tracer, bsub) - mpub, err := mqttpub.NewPublisher(fmt.Sprintf("mqtt://%s:%s", cfg.MQTTTargetHost, cfg.MQTTTargetPort), cfg.MQTTTargetUsername, cfg.MQTTTargetPassword, cfg.MQTTQoS, cfg.MQTTForwarderTimeout) + mpub, err := mqttpub.NewPublisher(fmt.Sprintf("mqtt://%s:%s", cfg.MQTTTargetHost, cfg.MQTTTargetPort), cfg.MQTTQoS, cfg.MQTTForwarderTimeout) if err != nil { logger.Error(fmt.Sprintf("failed to create MQTT publisher: %s", err)) exitCode = 1 @@ -225,10 +224,7 @@ func main() { go chc.CallHome(ctx) } - interceptor := interceptor{ - username: cfg.MQTTTargetUsername, - password: cfg.MQTTTargetPassword, - } + var interceptor session.Interceptor logger.Info(fmt.Sprintf("Starting MQTT proxy on port %s", cfg.MQTTPort)) g.Go(func() error { return proxyMQTT(ctx, cfg, logger, h, interceptor) @@ -324,25 +320,3 @@ func stopSignalHandler(ctx context.Context, cancel context.CancelFunc, logger *s return nil } } - -type interceptor struct { - username string - password string -} - -func (ic interceptor) Intercept(ctx context.Context, pkt packets.ControlPacket, dir session.Direction) (packets.ControlPacket, error) { - if connectPkt, ok := pkt.(*packets.ConnectPacket); ok { - if ic.username != "" { - connectPkt.Username = ic.username - connectPkt.UsernameFlag = true - } - if ic.password != "" { - connectPkt.Password = []byte(ic.password) - connectPkt.PasswordFlag = true - } - - return connectPkt, nil - } - - return pkt, nil -} diff --git a/cmd/rabbitmq-auth/main.go b/cmd/rabbitmq-auth/main.go new file mode 100644 index 0000000000..8e84b00050 --- /dev/null +++ b/cmd/rabbitmq-auth/main.go @@ -0,0 +1,123 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +// Package main contains rabbitmq-auth main function to start the rabbitmq-auth service. +package main + +import ( + "context" + "fmt" + "log" + "net/url" + "os" + + chclient "github.com/absmach/callhome/pkg/client" + "github.com/absmach/supermq" + smqlog "github.com/absmach/supermq/logger" + "github.com/absmach/supermq/pkg/grpcclient" + jaegerclient "github.com/absmach/supermq/pkg/jaeger" + "github.com/absmach/supermq/pkg/server" + httpserver "github.com/absmach/supermq/pkg/server/http" + "github.com/absmach/supermq/pkg/uuid" + "github.com/absmach/supermq/rabbitmq-auth/api" + "github.com/caarlos0/env/v11" + "golang.org/x/sync/errgroup" +) + +const ( + svcName = "rabbitmq-auth" + envPrefixClients = "SMQ_CLIENTS_AUTH_GRPC_" + envPrefixHTTP = "SMQ_RABBITMQ_AUTH_HTTP_" + defSvcHTTPPort = "9011" +) + +type config struct { + LogLevel string `env:"SMQ_RABBITMQ_AUTH_LOG_LEVEL" envDefault:"info"` + JaegerURL url.URL `env:"SMQ_JAEGER_URL" envDefault:"http://localhost:4318/v1/traces"` + SendTelemetry bool `env:"SMQ_SEND_TELEMETRY" envDefault:"true"` + InstanceID string `env:"SMQ_RABBITMQ_AUTH_INSTANCE_ID" envDefault:""` + TraceRatio float64 `env:"SMQ_JAEGER_TRACE_RATIO" envDefault:"1.0"` +} + +func main() { + ctx, cancel := context.WithCancel(context.Background()) + g, ctx := errgroup.WithContext(ctx) + + cfg := config{} + if err := env.Parse(&cfg); err != nil { + log.Fatalf("failed to load %s configuration : %s", svcName, err) + } + + logger, err := smqlog.New(os.Stdout, cfg.LogLevel) + if err != nil { + log.Fatalf("failed to init logger: %s", err.Error()) + } + + var exitCode int + defer smqlog.ExitWithError(&exitCode) + + if cfg.InstanceID == "" { + if cfg.InstanceID, err = uuid.New().ID(); err != nil { + logger.Error(fmt.Sprintf("failed to generate instanceID: %s", err)) + exitCode = 1 + return + } + } + + tp, err := jaegerclient.NewProvider(ctx, svcName, cfg.JaegerURL, cfg.InstanceID, cfg.TraceRatio) + if err != nil { + logger.Error(fmt.Sprintf("Failed to init Jaeger: %s", err)) + exitCode = 1 + return + } + defer func() { + if err := tp.Shutdown(ctx); err != nil { + logger.Error(fmt.Sprintf("Error shutting down tracer provider: %v", err)) + } + }() + + clientsClientCfg := grpcclient.Config{} + if err := env.ParseWithOptions(&clientsClientCfg, env.Options{Prefix: envPrefixClients}); err != nil { + logger.Error(fmt.Sprintf("failed to load %s auth configuration : %s", svcName, err)) + exitCode = 1 + return + } + + clientsClient, clientsHandler, err := grpcclient.SetupClientsClient(ctx, clientsClientCfg) + if err != nil { + logger.Error(err.Error()) + exitCode = 1 + return + } + defer clientsHandler.Close() + logger.Info("Clients service gRPC client successfully connected to clients gRPC server " + clientsHandler.Secure()) + + if cfg.SendTelemetry { + chc := chclient.New(svcName, supermq.Version, logger, cancel) + go chc.CallHome(ctx) + } + + httpServerConfig := server.Config{ + Port: defSvcHTTPPort, + } + if err := env.ParseWithOptions(&httpServerConfig, env.Options{Prefix: envPrefixHTTP}); err != nil { + logger.Error(fmt.Sprintf("failed to load %s HTTP server configuration : %s", svcName, err.Error())) + exitCode = 1 + return + } + httpServerConfig.Host = "" + + httpSrv := httpserver.NewServer(ctx, cancel, svcName, httpServerConfig, api.MakeHandler(logger, cfg.InstanceID, clientsClient), logger) + + g.Go(func() error { + return httpSrv.Start() + }) + + g.Go(func() error { + return server.StopSignalHandler(ctx, cancel, logger, svcName, httpSrv) + }) + + if err := g.Wait(); err != nil { + logger.Error(fmt.Sprintf("mProxy terminated: %s", err)) + } +} diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 2d9ef7e71e..e4c78f5235 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -881,6 +881,45 @@ services: networks: - supermq-base-net + rabbitmq-auth: + image: supermq/rabbitmq-auth:${SMQ_RELEASE_TAG} + container_name: supermq-rabbitmq-auth + depends_on: + - clients + restart: on-failure + ports: + - 9011:9011 + environment: + SMQ_RABBITMQ_AUTH_LOG_LEVEL: ${SMQ_RABBITMQ_AUTH_LOG_LEVEL} + SMQ_RABBITMQ_AUTH_INSTANCE_ID: ${SMQ_RABBITMQ_AUTH_INSTANCE_ID} + SMQ_CLIENTS_AUTH_GRPC_URL: ${SMQ_CLIENTS_AUTH_GRPC_URL} + SMQ_CLIENTS_AUTH_GRPC_TIMEOUT: ${SMQ_CLIENTS_AUTH_GRPC_TIMEOUT} + SMQ_CLIENTS_AUTH_GRPC_CLIENT_CERT: ${SMQ_CLIENTS_AUTH_GRPC_CLIENT_CERT:+/clients-grpc-client.crt} + SMQ_CLIENTS_AUTH_GRPC_CLIENT_KEY: ${SMQ_CLIENTS_AUTH_GRPC_CLIENT_KEY:+/clients-grpc-client.key} + SMQ_CLIENTS_AUTH_GRPC_SERVER_CA_CERTS: ${SMQ_CLIENTS_AUTH_GRPC_SERVER_CA_CERTS:+/clients-grpc-server-ca.crt} + SMQ_JAEGER_URL: ${SMQ_JAEGER_URL} + SMQ_JAEGER_TRACE_RATIO: ${SMQ_JAEGER_TRACE_RATIO} + SMQ_SEND_TELEMETRY: ${SMQ_SEND_TELEMETRY} + networks: + - supermq-base-net + volumes: + # Clients gRPC mTLS client certificates + - type: bind + source: ${SMQ_CLIENTS_AUTH_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert} + target: /clients-grpc-client${SMQ_CLIENTS_AUTH_GRPC_CLIENT_CERT:+.crt} + bind: + create_host_path: true + - type: bind + source: ${SMQ_CLIENTS_AUTH_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key} + target: /clients-grpc-client${SMQ_CLIENTS_AUTH_GRPC_CLIENT_KEY:+.key} + bind: + create_host_path: true + - type: bind + source: ${SMQ_CLIENTS_AUTH_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca} + target: /clients-grpc-server-ca${SMQ_CLIENTS_AUTH_GRPC_SERVER_CA_CERTS:+.crt} + bind: + create_host_path: true + mqtt-adapter: image: supermq/mqtt:${SMQ_RELEASE_TAG} container_name: supermq-mqtt diff --git a/docker/rabbitmq/enabled_plugins b/docker/rabbitmq/enabled_plugins index 5358cb0171..b2bf5cff41 100644 --- a/docker/rabbitmq/enabled_plugins +++ b/docker/rabbitmq/enabled_plugins @@ -1 +1 @@ -[rabbitmq_management,rabbitmq_mqtt]. +[rabbitmq_management,rabbitmq_mqtt,rabbitmq_auth_backend_http]. diff --git a/docker/rabbitmq/rabbitmq.conf b/docker/rabbitmq/rabbitmq.conf index 31e326c6b1..1e9bbc6cfd 100644 --- a/docker/rabbitmq/rabbitmq.conf +++ b/docker/rabbitmq/rabbitmq.conf @@ -11,5 +11,13 @@ loopback_users.guest = false ## a container log.console = true -## Enable anonymous connection -mqtt.allow_anonymous = true +# ## Enable anonymous connection +# mqtt.allow_anonymous = true + +## Auth Backend +auth_backends.1 = http +auth_http.http_method = get +auth_http.user_path = http://rabbitmq-auth:9011/auth/user +auth_http.vhost_path = http://rabbitmq-auth:9011/auth/vhost +auth_http.resource_path = http://rabbitmq-auth:9011/auth/resource +auth_http.topic_path = http://rabbitmq-auth:9011/auth/topic diff --git a/pkg/messaging/mqtt/publisher.go b/pkg/messaging/mqtt/publisher.go index ad51a04b89..54364c31de 100644 --- a/pkg/messaging/mqtt/publisher.go +++ b/pkg/messaging/mqtt/publisher.go @@ -23,8 +23,8 @@ type publisher struct { } // NewPublisher returns a new MQTT message publisher. -func NewPublisher(address, username, password string, qos uint8, timeout time.Duration) (messaging.Publisher, error) { - client, err := newClient(address, username, password, "mqtt-publisher", timeout) +func NewPublisher(address string, qos uint8, timeout time.Duration) (messaging.Publisher, error) { + client, err := newClient(address, "mqtt-publisher", timeout) if err != nil { return nil, err } diff --git a/pkg/messaging/mqtt/pubsub.go b/pkg/messaging/mqtt/pubsub.go index 7520673892..762439ce87 100644 --- a/pkg/messaging/mqtt/pubsub.go +++ b/pkg/messaging/mqtt/pubsub.go @@ -52,15 +52,13 @@ type pubsub struct { logger *slog.Logger mu sync.RWMutex address string - username string - password string timeout time.Duration subscriptions map[string]subscription } // NewPubSub returns MQTT message publisher/subscriber. -func NewPubSub(url, username, password string, qos uint8, timeout time.Duration, logger *slog.Logger) (messaging.PubSub, error) { - client, err := newClient(url, username, password, "mqtt-publisher", timeout) +func NewPubSub(url string, qos uint8, timeout time.Duration, logger *slog.Logger) (messaging.PubSub, error) { + client, err := newClient(url, "mqtt-publisher", timeout) if err != nil { return nil, err } @@ -71,8 +69,6 @@ func NewPubSub(url, username, password string, qos uint8, timeout time.Duration, qos: qos, }, address: url, - username: username, - password: password, timeout: timeout, logger: logger, subscriptions: make(map[string]subscription), @@ -100,7 +96,7 @@ func (ps *pubsub) Subscribe(ctx context.Context, cfg messaging.SubscriberConfig) } } default: - client, err := newClient(ps.address, ps.username, ps.password, cfg.ID, ps.timeout) + client, err := newClient(ps.address, cfg.ID, ps.timeout) if err != nil { return err } @@ -171,10 +167,10 @@ func (s *subscription) unsubscribe(topic string, timeout time.Duration) error { return token.Error() } -func newClient(address, username, password, id string, timeout time.Duration) (mqtt.Client, error) { +func newClient(address, id string, timeout time.Duration) (mqtt.Client, error) { opts := mqtt.NewClientOptions(). - SetUsername(username). - SetPassword(password). + SetConnectRetry(true). + SetAutoReconnect(true). AddBroker(address). SetClientID(id) client := mqtt.NewClient(opts) @@ -187,6 +183,8 @@ func newClient(address, username, password, id string, timeout time.Duration) (m return nil, ErrConnect } + fmt.Printf("Connected: %t\tError: %s\n", client.IsConnected(), token.Error()) + return client, nil } diff --git a/pkg/messaging/mqtt/setup_test.go b/pkg/messaging/mqtt/setup_test.go index 4a90012e01..9a01af691b 100644 --- a/pkg/messaging/mqtt/setup_test.go +++ b/pkg/messaging/mqtt/setup_test.go @@ -63,7 +63,7 @@ func TestMain(m *testing.M) { } if err := pool.Retry(func() error { - pubsub, err = mqttpubsub.NewPubSub(address, "supermq", "", 2, brokerTimeout, logger) + pubsub, err = mqttpubsub.NewPubSub(address, 2, brokerTimeout, logger) return err }); err != nil { log.Fatalf("Could not connect to docker: %s", err) diff --git a/rabbitmq-auth/api/transport.go b/rabbitmq-auth/api/transport.go new file mode 100644 index 0000000000..22466a39dd --- /dev/null +++ b/rabbitmq-auth/api/transport.go @@ -0,0 +1,70 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +package api + +import ( + "fmt" + "log/slog" + "net/http" + + "github.com/absmach/supermq" + grpcClientsV1 "github.com/absmach/supermq/api/grpc/clients/v1" + "github.com/go-chi/chi/v5" + "github.com/prometheus/client_golang/prometheus/promhttp" +) + +// MakeHandler returns a HTTP handler for API endpoints. +func MakeHandler(logger *slog.Logger, instanceID string, clients grpcClientsV1.ClientsServiceClient) http.Handler { + r := chi.NewRouter() + r.Route("/auth", func(r chi.Router) { + r.HandleFunc("/user", authTokenHandler(clients)) + r.Handle("/vhost", authTokenHandler(clients)) + r.Handle("/resource", authTokenHandler(clients)) + r.Handle("/topic", authTokenHandler(clients)) + }) + r.Get("/health", supermq.Health("http", instanceID)) + r.Handle("/metrics", promhttp.Handler()) + + return r +} + +func authTokenHandler(clients grpcClientsV1.ClientsServiceClient) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + // var password string + // var err error + // switch r.Method { + // case http.MethodPost: + // var req map[string]interface{} + // if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + // http.Error(w, err.Error(), http.StatusBadRequest) + // return + // } + // password = req["password"].(string) + // case http.MethodGet: + // password, err = apiutil.ReadStringQuery(r, "password", "") + // if err != nil { + // http.Error(w, err.Error(), http.StatusBadRequest) + // return + // } + // default: + // http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + // } + // fmt.Println(password) + // res, err := clients.Authenticate(r.Context(), &grpcClientsV1.AuthnReq{ClientSecret: password}) + // if err != nil { + // http.Error(w, err.Error(), http.StatusUnauthorized) + // return + // } + // if !res.GetAuthenticated() { + // http.Error(w, svcerr.ErrAuthentication.Error(), http.StatusUnauthorized) + // return + // } + fmt.Println(r.URL.Query()) + if _, err := w.Write([]byte("allow")); err != nil { + + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + } +} From 21c655ba308f7c00f0397111736703011f0999c8 Mon Sep 17 00:00:00 2001 From: Rodney Osodo Date: Tue, 11 Feb 2025 12:51:46 +0300 Subject: [PATCH 07/13] Revert "test: add auth handler POC" This reverts commit 87db3e61c56936d447f4fba91fec94b2e7071160. Signed-off-by: Rodney Osodo --- cmd/mqtt/main.go | 30 +++++++- cmd/rabbitmq-auth/main.go | 123 ------------------------------- docker/docker-compose.yml | 39 ---------- docker/rabbitmq/enabled_plugins | 2 +- docker/rabbitmq/rabbitmq.conf | 12 +-- pkg/messaging/mqtt/publisher.go | 4 +- pkg/messaging/mqtt/pubsub.go | 18 +++-- pkg/messaging/mqtt/setup_test.go | 2 +- rabbitmq-auth/api/transport.go | 70 ------------------ 9 files changed, 44 insertions(+), 256 deletions(-) delete mode 100644 cmd/rabbitmq-auth/main.go delete mode 100644 rabbitmq-auth/api/transport.go diff --git a/cmd/mqtt/main.go b/cmd/mqtt/main.go index b961ad4aa2..9fccd4c2d9 100644 --- a/cmd/mqtt/main.go +++ b/cmd/mqtt/main.go @@ -39,6 +39,7 @@ import ( "github.com/absmach/supermq/pkg/uuid" "github.com/caarlos0/env/v11" "github.com/cenkalti/backoff/v4" + "github.com/eclipse/paho.mqtt.golang/packets" "golang.org/x/sync/errgroup" ) @@ -137,7 +138,7 @@ func main() { defer bsub.Close() bsub = brokerstracing.NewPubSub(serverConfig, tracer, bsub) - mpub, err := mqttpub.NewPublisher(fmt.Sprintf("mqtt://%s:%s", cfg.MQTTTargetHost, cfg.MQTTTargetPort), cfg.MQTTQoS, cfg.MQTTForwarderTimeout) + mpub, err := mqttpub.NewPublisher(fmt.Sprintf("mqtt://%s:%s", cfg.MQTTTargetHost, cfg.MQTTTargetPort), cfg.MQTTTargetUsername, cfg.MQTTTargetPassword, cfg.MQTTQoS, cfg.MQTTForwarderTimeout) if err != nil { logger.Error(fmt.Sprintf("failed to create MQTT publisher: %s", err)) exitCode = 1 @@ -224,7 +225,10 @@ func main() { go chc.CallHome(ctx) } - var interceptor session.Interceptor + interceptor := interceptor{ + username: cfg.MQTTTargetUsername, + password: cfg.MQTTTargetPassword, + } logger.Info(fmt.Sprintf("Starting MQTT proxy on port %s", cfg.MQTTPort)) g.Go(func() error { return proxyMQTT(ctx, cfg, logger, h, interceptor) @@ -320,3 +324,25 @@ func stopSignalHandler(ctx context.Context, cancel context.CancelFunc, logger *s return nil } } + +type interceptor struct { + username string + password string +} + +func (ic interceptor) Intercept(ctx context.Context, pkt packets.ControlPacket, dir session.Direction) (packets.ControlPacket, error) { + if connectPkt, ok := pkt.(*packets.ConnectPacket); ok { + if ic.username != "" { + connectPkt.Username = ic.username + connectPkt.UsernameFlag = true + } + if ic.password != "" { + connectPkt.Password = []byte(ic.password) + connectPkt.PasswordFlag = true + } + + return connectPkt, nil + } + + return pkt, nil +} diff --git a/cmd/rabbitmq-auth/main.go b/cmd/rabbitmq-auth/main.go deleted file mode 100644 index 8e84b00050..0000000000 --- a/cmd/rabbitmq-auth/main.go +++ /dev/null @@ -1,123 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -// Package main contains rabbitmq-auth main function to start the rabbitmq-auth service. -package main - -import ( - "context" - "fmt" - "log" - "net/url" - "os" - - chclient "github.com/absmach/callhome/pkg/client" - "github.com/absmach/supermq" - smqlog "github.com/absmach/supermq/logger" - "github.com/absmach/supermq/pkg/grpcclient" - jaegerclient "github.com/absmach/supermq/pkg/jaeger" - "github.com/absmach/supermq/pkg/server" - httpserver "github.com/absmach/supermq/pkg/server/http" - "github.com/absmach/supermq/pkg/uuid" - "github.com/absmach/supermq/rabbitmq-auth/api" - "github.com/caarlos0/env/v11" - "golang.org/x/sync/errgroup" -) - -const ( - svcName = "rabbitmq-auth" - envPrefixClients = "SMQ_CLIENTS_AUTH_GRPC_" - envPrefixHTTP = "SMQ_RABBITMQ_AUTH_HTTP_" - defSvcHTTPPort = "9011" -) - -type config struct { - LogLevel string `env:"SMQ_RABBITMQ_AUTH_LOG_LEVEL" envDefault:"info"` - JaegerURL url.URL `env:"SMQ_JAEGER_URL" envDefault:"http://localhost:4318/v1/traces"` - SendTelemetry bool `env:"SMQ_SEND_TELEMETRY" envDefault:"true"` - InstanceID string `env:"SMQ_RABBITMQ_AUTH_INSTANCE_ID" envDefault:""` - TraceRatio float64 `env:"SMQ_JAEGER_TRACE_RATIO" envDefault:"1.0"` -} - -func main() { - ctx, cancel := context.WithCancel(context.Background()) - g, ctx := errgroup.WithContext(ctx) - - cfg := config{} - if err := env.Parse(&cfg); err != nil { - log.Fatalf("failed to load %s configuration : %s", svcName, err) - } - - logger, err := smqlog.New(os.Stdout, cfg.LogLevel) - if err != nil { - log.Fatalf("failed to init logger: %s", err.Error()) - } - - var exitCode int - defer smqlog.ExitWithError(&exitCode) - - if cfg.InstanceID == "" { - if cfg.InstanceID, err = uuid.New().ID(); err != nil { - logger.Error(fmt.Sprintf("failed to generate instanceID: %s", err)) - exitCode = 1 - return - } - } - - tp, err := jaegerclient.NewProvider(ctx, svcName, cfg.JaegerURL, cfg.InstanceID, cfg.TraceRatio) - if err != nil { - logger.Error(fmt.Sprintf("Failed to init Jaeger: %s", err)) - exitCode = 1 - return - } - defer func() { - if err := tp.Shutdown(ctx); err != nil { - logger.Error(fmt.Sprintf("Error shutting down tracer provider: %v", err)) - } - }() - - clientsClientCfg := grpcclient.Config{} - if err := env.ParseWithOptions(&clientsClientCfg, env.Options{Prefix: envPrefixClients}); err != nil { - logger.Error(fmt.Sprintf("failed to load %s auth configuration : %s", svcName, err)) - exitCode = 1 - return - } - - clientsClient, clientsHandler, err := grpcclient.SetupClientsClient(ctx, clientsClientCfg) - if err != nil { - logger.Error(err.Error()) - exitCode = 1 - return - } - defer clientsHandler.Close() - logger.Info("Clients service gRPC client successfully connected to clients gRPC server " + clientsHandler.Secure()) - - if cfg.SendTelemetry { - chc := chclient.New(svcName, supermq.Version, logger, cancel) - go chc.CallHome(ctx) - } - - httpServerConfig := server.Config{ - Port: defSvcHTTPPort, - } - if err := env.ParseWithOptions(&httpServerConfig, env.Options{Prefix: envPrefixHTTP}); err != nil { - logger.Error(fmt.Sprintf("failed to load %s HTTP server configuration : %s", svcName, err.Error())) - exitCode = 1 - return - } - httpServerConfig.Host = "" - - httpSrv := httpserver.NewServer(ctx, cancel, svcName, httpServerConfig, api.MakeHandler(logger, cfg.InstanceID, clientsClient), logger) - - g.Go(func() error { - return httpSrv.Start() - }) - - g.Go(func() error { - return server.StopSignalHandler(ctx, cancel, logger, svcName, httpSrv) - }) - - if err := g.Wait(); err != nil { - logger.Error(fmt.Sprintf("mProxy terminated: %s", err)) - } -} diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index e4c78f5235..2d9ef7e71e 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -881,45 +881,6 @@ services: networks: - supermq-base-net - rabbitmq-auth: - image: supermq/rabbitmq-auth:${SMQ_RELEASE_TAG} - container_name: supermq-rabbitmq-auth - depends_on: - - clients - restart: on-failure - ports: - - 9011:9011 - environment: - SMQ_RABBITMQ_AUTH_LOG_LEVEL: ${SMQ_RABBITMQ_AUTH_LOG_LEVEL} - SMQ_RABBITMQ_AUTH_INSTANCE_ID: ${SMQ_RABBITMQ_AUTH_INSTANCE_ID} - SMQ_CLIENTS_AUTH_GRPC_URL: ${SMQ_CLIENTS_AUTH_GRPC_URL} - SMQ_CLIENTS_AUTH_GRPC_TIMEOUT: ${SMQ_CLIENTS_AUTH_GRPC_TIMEOUT} - SMQ_CLIENTS_AUTH_GRPC_CLIENT_CERT: ${SMQ_CLIENTS_AUTH_GRPC_CLIENT_CERT:+/clients-grpc-client.crt} - SMQ_CLIENTS_AUTH_GRPC_CLIENT_KEY: ${SMQ_CLIENTS_AUTH_GRPC_CLIENT_KEY:+/clients-grpc-client.key} - SMQ_CLIENTS_AUTH_GRPC_SERVER_CA_CERTS: ${SMQ_CLIENTS_AUTH_GRPC_SERVER_CA_CERTS:+/clients-grpc-server-ca.crt} - SMQ_JAEGER_URL: ${SMQ_JAEGER_URL} - SMQ_JAEGER_TRACE_RATIO: ${SMQ_JAEGER_TRACE_RATIO} - SMQ_SEND_TELEMETRY: ${SMQ_SEND_TELEMETRY} - networks: - - supermq-base-net - volumes: - # Clients gRPC mTLS client certificates - - type: bind - source: ${SMQ_CLIENTS_AUTH_GRPC_CLIENT_CERT:-ssl/certs/dummy/client_cert} - target: /clients-grpc-client${SMQ_CLIENTS_AUTH_GRPC_CLIENT_CERT:+.crt} - bind: - create_host_path: true - - type: bind - source: ${SMQ_CLIENTS_AUTH_GRPC_CLIENT_KEY:-ssl/certs/dummy/client_key} - target: /clients-grpc-client${SMQ_CLIENTS_AUTH_GRPC_CLIENT_KEY:+.key} - bind: - create_host_path: true - - type: bind - source: ${SMQ_CLIENTS_AUTH_GRPC_SERVER_CA_CERTS:-ssl/certs/dummy/server_ca} - target: /clients-grpc-server-ca${SMQ_CLIENTS_AUTH_GRPC_SERVER_CA_CERTS:+.crt} - bind: - create_host_path: true - mqtt-adapter: image: supermq/mqtt:${SMQ_RELEASE_TAG} container_name: supermq-mqtt diff --git a/docker/rabbitmq/enabled_plugins b/docker/rabbitmq/enabled_plugins index b2bf5cff41..5358cb0171 100644 --- a/docker/rabbitmq/enabled_plugins +++ b/docker/rabbitmq/enabled_plugins @@ -1 +1 @@ -[rabbitmq_management,rabbitmq_mqtt,rabbitmq_auth_backend_http]. +[rabbitmq_management,rabbitmq_mqtt]. diff --git a/docker/rabbitmq/rabbitmq.conf b/docker/rabbitmq/rabbitmq.conf index 1e9bbc6cfd..31e326c6b1 100644 --- a/docker/rabbitmq/rabbitmq.conf +++ b/docker/rabbitmq/rabbitmq.conf @@ -11,13 +11,5 @@ loopback_users.guest = false ## a container log.console = true -# ## Enable anonymous connection -# mqtt.allow_anonymous = true - -## Auth Backend -auth_backends.1 = http -auth_http.http_method = get -auth_http.user_path = http://rabbitmq-auth:9011/auth/user -auth_http.vhost_path = http://rabbitmq-auth:9011/auth/vhost -auth_http.resource_path = http://rabbitmq-auth:9011/auth/resource -auth_http.topic_path = http://rabbitmq-auth:9011/auth/topic +## Enable anonymous connection +mqtt.allow_anonymous = true diff --git a/pkg/messaging/mqtt/publisher.go b/pkg/messaging/mqtt/publisher.go index 54364c31de..ad51a04b89 100644 --- a/pkg/messaging/mqtt/publisher.go +++ b/pkg/messaging/mqtt/publisher.go @@ -23,8 +23,8 @@ type publisher struct { } // NewPublisher returns a new MQTT message publisher. -func NewPublisher(address string, qos uint8, timeout time.Duration) (messaging.Publisher, error) { - client, err := newClient(address, "mqtt-publisher", timeout) +func NewPublisher(address, username, password string, qos uint8, timeout time.Duration) (messaging.Publisher, error) { + client, err := newClient(address, username, password, "mqtt-publisher", timeout) if err != nil { return nil, err } diff --git a/pkg/messaging/mqtt/pubsub.go b/pkg/messaging/mqtt/pubsub.go index 762439ce87..7520673892 100644 --- a/pkg/messaging/mqtt/pubsub.go +++ b/pkg/messaging/mqtt/pubsub.go @@ -52,13 +52,15 @@ type pubsub struct { logger *slog.Logger mu sync.RWMutex address string + username string + password string timeout time.Duration subscriptions map[string]subscription } // NewPubSub returns MQTT message publisher/subscriber. -func NewPubSub(url string, qos uint8, timeout time.Duration, logger *slog.Logger) (messaging.PubSub, error) { - client, err := newClient(url, "mqtt-publisher", timeout) +func NewPubSub(url, username, password string, qos uint8, timeout time.Duration, logger *slog.Logger) (messaging.PubSub, error) { + client, err := newClient(url, username, password, "mqtt-publisher", timeout) if err != nil { return nil, err } @@ -69,6 +71,8 @@ func NewPubSub(url string, qos uint8, timeout time.Duration, logger *slog.Logger qos: qos, }, address: url, + username: username, + password: password, timeout: timeout, logger: logger, subscriptions: make(map[string]subscription), @@ -96,7 +100,7 @@ func (ps *pubsub) Subscribe(ctx context.Context, cfg messaging.SubscriberConfig) } } default: - client, err := newClient(ps.address, cfg.ID, ps.timeout) + client, err := newClient(ps.address, ps.username, ps.password, cfg.ID, ps.timeout) if err != nil { return err } @@ -167,10 +171,10 @@ func (s *subscription) unsubscribe(topic string, timeout time.Duration) error { return token.Error() } -func newClient(address, id string, timeout time.Duration) (mqtt.Client, error) { +func newClient(address, username, password, id string, timeout time.Duration) (mqtt.Client, error) { opts := mqtt.NewClientOptions(). - SetConnectRetry(true). - SetAutoReconnect(true). + SetUsername(username). + SetPassword(password). AddBroker(address). SetClientID(id) client := mqtt.NewClient(opts) @@ -183,8 +187,6 @@ func newClient(address, id string, timeout time.Duration) (mqtt.Client, error) { return nil, ErrConnect } - fmt.Printf("Connected: %t\tError: %s\n", client.IsConnected(), token.Error()) - return client, nil } diff --git a/pkg/messaging/mqtt/setup_test.go b/pkg/messaging/mqtt/setup_test.go index 9a01af691b..4a90012e01 100644 --- a/pkg/messaging/mqtt/setup_test.go +++ b/pkg/messaging/mqtt/setup_test.go @@ -63,7 +63,7 @@ func TestMain(m *testing.M) { } if err := pool.Retry(func() error { - pubsub, err = mqttpubsub.NewPubSub(address, 2, brokerTimeout, logger) + pubsub, err = mqttpubsub.NewPubSub(address, "supermq", "", 2, brokerTimeout, logger) return err }); err != nil { log.Fatalf("Could not connect to docker: %s", err) diff --git a/rabbitmq-auth/api/transport.go b/rabbitmq-auth/api/transport.go deleted file mode 100644 index 22466a39dd..0000000000 --- a/rabbitmq-auth/api/transport.go +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -package api - -import ( - "fmt" - "log/slog" - "net/http" - - "github.com/absmach/supermq" - grpcClientsV1 "github.com/absmach/supermq/api/grpc/clients/v1" - "github.com/go-chi/chi/v5" - "github.com/prometheus/client_golang/prometheus/promhttp" -) - -// MakeHandler returns a HTTP handler for API endpoints. -func MakeHandler(logger *slog.Logger, instanceID string, clients grpcClientsV1.ClientsServiceClient) http.Handler { - r := chi.NewRouter() - r.Route("/auth", func(r chi.Router) { - r.HandleFunc("/user", authTokenHandler(clients)) - r.Handle("/vhost", authTokenHandler(clients)) - r.Handle("/resource", authTokenHandler(clients)) - r.Handle("/topic", authTokenHandler(clients)) - }) - r.Get("/health", supermq.Health("http", instanceID)) - r.Handle("/metrics", promhttp.Handler()) - - return r -} - -func authTokenHandler(clients grpcClientsV1.ClientsServiceClient) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - // var password string - // var err error - // switch r.Method { - // case http.MethodPost: - // var req map[string]interface{} - // if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - // http.Error(w, err.Error(), http.StatusBadRequest) - // return - // } - // password = req["password"].(string) - // case http.MethodGet: - // password, err = apiutil.ReadStringQuery(r, "password", "") - // if err != nil { - // http.Error(w, err.Error(), http.StatusBadRequest) - // return - // } - // default: - // http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) - // } - // fmt.Println(password) - // res, err := clients.Authenticate(r.Context(), &grpcClientsV1.AuthnReq{ClientSecret: password}) - // if err != nil { - // http.Error(w, err.Error(), http.StatusUnauthorized) - // return - // } - // if !res.GetAuthenticated() { - // http.Error(w, svcerr.ErrAuthentication.Error(), http.StatusUnauthorized) - // return - // } - fmt.Println(r.URL.Query()) - if _, err := w.Write([]byte("allow")); err != nil { - - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - } -} From 79b11ef91361512b4957b81311613e362938a2a6 Mon Sep 17 00:00:00 2001 From: Rodney Osodo Date: Tue, 11 Feb 2025 12:58:52 +0300 Subject: [PATCH 08/13] chore: update go modules Signed-off-by: Rodney Osodo --- go.mod | 10 +++++----- go.sum | 12 ++++++++++++ 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index 53a0ae7bad..82487afbc8 100644 --- a/go.mod +++ b/go.mod @@ -17,10 +17,10 @@ require ( github.com/fatih/color v1.18.0 github.com/go-chi/chi/v5 v5.2.1 github.com/go-kit/kit v0.13.0 - github.com/gofrs/uuid/v5 v5.3.0 + github.com/gofrs/uuid/v5 v5.3.1 github.com/google/uuid v1.6.0 github.com/gorilla/websocket v1.5.3 - github.com/hashicorp/vault/api v1.15.0 + github.com/hashicorp/vault/api v1.16.0 github.com/hashicorp/vault/api/auth/approle v0.8.0 github.com/hokaccha/go-prettyjson v0.0.0-20211117102719-0474bc63780f github.com/jackc/pgtype v1.14.4 @@ -125,7 +125,7 @@ require ( github.com/moby/docker-image-spec v1.3.1 // indirect github.com/moby/term v0.5.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/nats-io/nkeys v0.4.9 // indirect + github.com/nats-io/nkeys v0.4.10 // indirect github.com/nats-io/nuid v1.0.1 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // indirect @@ -156,8 +156,8 @@ require ( go.opentelemetry.io/otel/metric v1.34.0 // indirect go.opentelemetry.io/proto/otlp v1.5.0 // indirect go.uber.org/atomic v1.11.0 // indirect - golang.org/x/exp v0.0.0-20250207012021-f9890c6ad9f3 // indirect - golang.org/x/net v0.34.0 // indirect + golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac // indirect + golang.org/x/net v0.35.0 // indirect golang.org/x/sys v0.30.0 // indirect golang.org/x/text v0.22.0 // indirect golang.org/x/time v0.10.0 // indirect diff --git a/go.sum b/go.sum index b39e5cf59e..8abe6dad8f 100644 --- a/go.sum +++ b/go.sum @@ -129,8 +129,11 @@ github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= github.com/gofrs/uuid/v5 v5.3.0 h1:m0mUMr+oVYUdxpMLgSYCZiXe7PuVPnI94+OMeVBNedk= github.com/gofrs/uuid/v5 v5.3.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8= +github.com/gofrs/uuid/v5 v5.3.1 h1:aPx49MwJbekCzOyhZDjJVb0hx3A0KLjlbLx6p2gY0p0= +github.com/gofrs/uuid/v5 v5.3.1/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= @@ -181,6 +184,8 @@ github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/vault/api v1.15.0 h1:O24FYQCWwhwKnF7CuSqP30S51rTV7vz1iACXE/pj5DA= github.com/hashicorp/vault/api v1.15.0/go.mod h1:+5YTO09JGn0u+b6ySD/LLVf8WkJCPLAL2Vkmrn2+CM8= +github.com/hashicorp/vault/api v1.16.0 h1:nbEYGJiAPGzT9U4oWgaaB0g+Rj8E59QuHKyA5LhwQN4= +github.com/hashicorp/vault/api v1.16.0/go.mod h1:KhuUhzOD8lDSk29AtzNjgAu2kxRA9jL9NAbkFlqvkBA= github.com/hashicorp/vault/api/auth/approle v0.8.0 h1:FuVtWZ0xD6+wz1x0l5s0b4852RmVXQNEiKhVXt6lfQY= github.com/hashicorp/vault/api/auth/approle v0.8.0/go.mod h1:NV7O9r5JUtNdVnqVZeMHva81AIdpG0WoIQohNt1VCPM= github.com/hokaccha/go-prettyjson v0.0.0-20211117102719-0474bc63780f h1:7LYC+Yfkj3CTRcShK0KOL/w6iTiKyqqBA9a41Wnggw8= @@ -239,6 +244,7 @@ github.com/jackc/pgx/v5 v5.7.2/go.mod h1:ncY89UGWxg82EykZUwSpUKEfccBGGYq1xjrOpsb github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.3.0 h1:eHK/5clGOatcjX3oWGBO/MpxpbHzSwud5EWTSCI+MX0= github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= @@ -310,6 +316,8 @@ github.com/nats-io/nats.go v1.39.0 h1:2/yg2JQjiYYKLwDuBzV0FbB2sIV+eFNkEevlRi4n9l github.com/nats-io/nats.go v1.39.0/go.mod h1:MgRb8oOdigA6cYpEPhXJuRVH6UE/V4jblJ2jQ27IXYM= github.com/nats-io/nkeys v0.4.9 h1:qe9Faq2Gxwi6RZnZMXfmGMZkg3afLLOtrU+gDZJ35b0= github.com/nats-io/nkeys v0.4.9/go.mod h1:jcMqs+FLG+W5YO36OX6wFIFcmpdAns+w1Wm6D3I/evE= +github.com/nats-io/nkeys v0.4.10 h1:glmRrpCmYLHByYcePvnTBEAwawwapjCPMjy2huw20wc= +github.com/nats-io/nkeys v0.4.10/go.mod h1:OjRrnIKnWBFl+s4YK5ChQfvHP2fxqZexrKJoVVyWB3U= github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/oklog/ulid/v2 v2.1.0 h1:+9lhoxAP56we25tyYETBBY1YLA2SaoLvUFgrP2miPJU= @@ -486,6 +494,8 @@ golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20250207012021-f9890c6ad9f3 h1:qNgPs5exUA+G0C96DrPwNrvLSj7GT/9D+3WMWUcUg34= golang.org/x/exp v0.0.0-20250207012021-f9890c6ad9f3/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU= +golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac h1:l5+whBCLH3iH2ZNHYLbAe58bo7yrN4mVcnkHDYz5vvs= +golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac/go.mod h1:hH+7mtFmImwwcMvScyxUhjuVHR3HGaDPMn9rMSUUbxo= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= @@ -512,6 +522,8 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= +golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= +golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.26.0 h1:afQXWNNaeC4nvZ0Ed9XvCCzXM6UHJG7iCg0W4fPqSBE= golang.org/x/oauth2 v0.26.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= From 7abb72b689e7594f8241798b973f1831140b0fa5 Mon Sep 17 00:00:00 2001 From: Rodney Osodo Date: Tue, 11 Feb 2025 13:06:37 +0300 Subject: [PATCH 09/13] chore: update docker images for jaeger, nats and rabbitmq Use rabbitMQ alpine rather than management-alpine since we are enabling that feature using config files Signed-off-by: Rodney Osodo --- docker/docker-compose.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 2d9ef7e71e..9655ce3647 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -870,7 +870,7 @@ services: create_host_path: true jaeger: - image: jaegertracing/all-in-one:1.60 + image: jaegertracing/all-in-one:1.66.0 container_name: supermq-jaeger environment: COLLECTOR_OTLP_ENABLED: ${SMQ_JAEGER_COLLECTOR_OTLP_ENABLED} @@ -1210,7 +1210,7 @@ services: create_host_path: true rabbitmq: - image: rabbitmq:4.0.5-management-alpine + image: rabbitmq:4.0.5-alpine container_name: supermq-rabbitmq restart: on-failure environment: @@ -1230,7 +1230,7 @@ services: - supermq-base-net nats: - image: nats:2.10.9-alpine + image: nats:2.10.25-alpine container_name: supermq-nats restart: on-failure command: "--config=/etc/nats/nats.conf" From 1f632bb829c7226375a62039853c32ee306854c2 Mon Sep 17 00:00:00 2001 From: Rodney Osodo Date: Tue, 11 Feb 2025 13:28:11 +0300 Subject: [PATCH 10/13] fix: add autoreconnect and retry connect when creating new mqtt client Signed-off-by: Rodney Osodo --- go.sum | 12 ------------ pkg/messaging/mqtt/pubsub.go | 2 ++ 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/go.sum b/go.sum index 8abe6dad8f..ff8df1547c 100644 --- a/go.sum +++ b/go.sum @@ -129,9 +129,6 @@ github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= -github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= -github.com/gofrs/uuid/v5 v5.3.0 h1:m0mUMr+oVYUdxpMLgSYCZiXe7PuVPnI94+OMeVBNedk= -github.com/gofrs/uuid/v5 v5.3.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8= github.com/gofrs/uuid/v5 v5.3.1 h1:aPx49MwJbekCzOyhZDjJVb0hx3A0KLjlbLx6p2gY0p0= github.com/gofrs/uuid/v5 v5.3.1/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= @@ -182,8 +179,6 @@ github.com/hashicorp/go-sockaddr v1.0.7 h1:G+pTkSO01HpR5qCxg7lxfsFEZaG+C0VssTy/9 github.com/hashicorp/go-sockaddr v1.0.7/go.mod h1:FZQbEYa1pxkQ7WLpyXJ6cbjpT8q0YgQaK/JakXqGyWw= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hashicorp/vault/api v1.15.0 h1:O24FYQCWwhwKnF7CuSqP30S51rTV7vz1iACXE/pj5DA= -github.com/hashicorp/vault/api v1.15.0/go.mod h1:+5YTO09JGn0u+b6ySD/LLVf8WkJCPLAL2Vkmrn2+CM8= github.com/hashicorp/vault/api v1.16.0 h1:nbEYGJiAPGzT9U4oWgaaB0g+Rj8E59QuHKyA5LhwQN4= github.com/hashicorp/vault/api v1.16.0/go.mod h1:KhuUhzOD8lDSk29AtzNjgAu2kxRA9jL9NAbkFlqvkBA= github.com/hashicorp/vault/api/auth/approle v0.8.0 h1:FuVtWZ0xD6+wz1x0l5s0b4852RmVXQNEiKhVXt6lfQY= @@ -244,7 +239,6 @@ github.com/jackc/pgx/v5 v5.7.2/go.mod h1:ncY89UGWxg82EykZUwSpUKEfccBGGYq1xjrOpsb github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jackc/puddle v1.3.0 h1:eHK/5clGOatcjX3oWGBO/MpxpbHzSwud5EWTSCI+MX0= github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= @@ -314,8 +308,6 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/nats-io/nats.go v1.39.0 h1:2/yg2JQjiYYKLwDuBzV0FbB2sIV+eFNkEevlRi4n9lI= github.com/nats-io/nats.go v1.39.0/go.mod h1:MgRb8oOdigA6cYpEPhXJuRVH6UE/V4jblJ2jQ27IXYM= -github.com/nats-io/nkeys v0.4.9 h1:qe9Faq2Gxwi6RZnZMXfmGMZkg3afLLOtrU+gDZJ35b0= -github.com/nats-io/nkeys v0.4.9/go.mod h1:jcMqs+FLG+W5YO36OX6wFIFcmpdAns+w1Wm6D3I/evE= github.com/nats-io/nkeys v0.4.10 h1:glmRrpCmYLHByYcePvnTBEAwawwapjCPMjy2huw20wc= github.com/nats-io/nkeys v0.4.10/go.mod h1:OjRrnIKnWBFl+s4YK5ChQfvHP2fxqZexrKJoVVyWB3U= github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= @@ -492,8 +484,6 @@ golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZP golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20250207012021-f9890c6ad9f3 h1:qNgPs5exUA+G0C96DrPwNrvLSj7GT/9D+3WMWUcUg34= -golang.org/x/exp v0.0.0-20250207012021-f9890c6ad9f3/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU= golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac h1:l5+whBCLH3iH2ZNHYLbAe58bo7yrN4mVcnkHDYz5vvs= golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac/go.mod h1:hH+7mtFmImwwcMvScyxUhjuVHR3HGaDPMn9rMSUUbxo= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -520,8 +510,6 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= -golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= -golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= diff --git a/pkg/messaging/mqtt/pubsub.go b/pkg/messaging/mqtt/pubsub.go index 7520673892..273652eca6 100644 --- a/pkg/messaging/mqtt/pubsub.go +++ b/pkg/messaging/mqtt/pubsub.go @@ -175,6 +175,8 @@ func newClient(address, username, password, id string, timeout time.Duration) (m opts := mqtt.NewClientOptions(). SetUsername(username). SetPassword(password). + SetConnectRetry(true). + SetAutoReconnect(true). AddBroker(address). SetClientID(id) client := mqtt.NewClient(opts) From 4a559cfe4e7ddc1d7fa85d4d08762916598e8596 Mon Sep 17 00:00:00 2001 From: Rodney Osodo Date: Tue, 11 Feb 2025 16:25:54 +0300 Subject: [PATCH 11/13] feat: use RabbitMQ as default MQTT and Message Broker Signed-off-by: Rodney Osodo --- Makefile | 4 +- cmd/auth/main.go | 2 +- cmd/channels/main.go | 2 +- cmd/clients/main.go | 2 +- cmd/coap/main.go | 4 +- cmd/domains/main.go | 2 +- cmd/groups/main.go | 2 +- cmd/http/main.go | 4 +- cmd/journal/main.go | 2 +- cmd/mqtt/main.go | 4 +- cmd/users/main.go | 2 +- cmd/ws/main.go | 4 +- coap/README.md | 44 +++++----- docker/.env | 4 +- docker/README.md | 22 ++--- docker/docker-compose.yml | 33 ++----- http/README.md | 36 ++++---- mqtt/README.md | 54 ++++++------ pkg/messaging/README.md | 4 +- pkg/messaging/brokers/brokers_nats.go | 4 +- pkg/messaging/brokers/brokers_rabbitmq.go | 4 +- pkg/messaging/brokers/tracing/brokers_nats.go | 4 +- .../brokers/tracing/brokers_rabbitmq.go | 4 +- tools/config/golangci.yml | 2 +- users/README.md | 88 +++++++++---------- ws/README.md | 36 ++++---- 26 files changed, 175 insertions(+), 198 deletions(-) diff --git a/Makefile b/Makefile index 40dddcacee..31fe525fad 100644 --- a/Makefile +++ b/Makefile @@ -29,13 +29,13 @@ INTERNAL_PROTO_FILES := $(shell find $(INTERNAL_PROTO_DIR) -name "*.proto" | sed ifneq ($(SMQ_MESSAGE_BROKER_TYPE),) SMQ_MESSAGE_BROKER_TYPE := $(SMQ_MESSAGE_BROKER_TYPE) else - SMQ_MESSAGE_BROKER_TYPE=nats + SMQ_MESSAGE_BROKER_TYPE=rabbitmq endif ifneq ($(SMQ_ES_TYPE),) SMQ_ES_TYPE := $(SMQ_ES_TYPE) else - SMQ_ES_TYPE=nats + SMQ_ES_TYPE=rabbitmq endif define compile_service diff --git a/cmd/auth/main.go b/cmd/auth/main.go index c5ef6d2b5e..1e12744e2a 100644 --- a/cmd/auth/main.go +++ b/cmd/auth/main.go @@ -74,7 +74,7 @@ type config struct { SpicedbSchemaFile string `env:"SMQ_SPICEDB_SCHEMA_FILE" envDefault:"./docker/spicedb/schema.zed"` SpicedbPreSharedKey string `env:"SMQ_SPICEDB_PRE_SHARED_KEY" envDefault:"12345678"` TraceRatio float64 `env:"SMQ_JAEGER_TRACE_RATIO" envDefault:"1.0"` - ESURL string `env:"SMQ_ES_URL" envDefault:"nats://localhost:4222"` + ESURL string `env:"SMQ_ES_URL" envDefault:"amqp://guest:guest@rabbitmq:5672/"` } func main() { diff --git a/cmd/channels/main.go b/cmd/channels/main.go index 8b9c56c08d..d41ca17c72 100644 --- a/cmd/channels/main.go +++ b/cmd/channels/main.go @@ -79,7 +79,7 @@ type config struct { InstanceID string `env:"SMQ_CHANNELS_INSTANCE_ID" envDefault:""` JaegerURL url.URL `env:"SMQ_JAEGER_URL" envDefault:"http://localhost:4318/v1/traces"` SendTelemetry bool `env:"SMQ_SEND_TELEMETRY" envDefault:"true"` - ESURL string `env:"SMQ_ES_URL" envDefault:"nats://localhost:4222"` + ESURL string `env:"SMQ_ES_URL" envDefault:"amqp://guest:guest@rabbitmq:5672/"` ESConsumerName string `env:"SMQ_CHANNELS_EVENT_CONSUMER" envDefault:"channels"` TraceRatio float64 `env:"SMQ_JAEGER_TRACE_RATIO" envDefault:"1.0"` SpicedbHost string `env:"SMQ_SPICEDB_HOST" envDefault:"localhost"` diff --git a/cmd/clients/main.go b/cmd/clients/main.go index b3030976cf..02453d37d0 100644 --- a/cmd/clients/main.go +++ b/cmd/clients/main.go @@ -87,7 +87,7 @@ type config struct { CacheKeyDuration time.Duration `env:"SMQ_CLIENTS_CACHE_KEY_DURATION" envDefault:"10m"` JaegerURL url.URL `env:"SMQ_JAEGER_URL" envDefault:"http://localhost:4318/v1/traces"` SendTelemetry bool `env:"SMQ_SEND_TELEMETRY" envDefault:"true"` - ESURL string `env:"SMQ_ES_URL" envDefault:"nats://localhost:4222"` + ESURL string `env:"SMQ_ES_URL" envDefault:"amqp://guest:guest@rabbitmq:5672/"` ESConsumerName string `env:"SMQ_CLIENTS_EVENT_CONSUMER" envDefault:"clients"` TraceRatio float64 `env:"SMQ_JAEGER_TRACE_RATIO" envDefault:"1.0"` SpicedbHost string `env:"SMQ_SPICEDB_HOST" envDefault:"localhost"` diff --git a/cmd/coap/main.go b/cmd/coap/main.go index 8dae202479..b3837b27d2 100644 --- a/cmd/coap/main.go +++ b/cmd/coap/main.go @@ -43,12 +43,12 @@ const ( type config struct { LogLevel string `env:"SMQ_COAP_ADAPTER_LOG_LEVEL" envDefault:"info"` - BrokerURL string `env:"SMQ_MESSAGE_BROKER_URL" envDefault:"nats://localhost:4222"` + BrokerURL string `env:"SMQ_MESSAGE_BROKER_URL" envDefault:"amqp://guest:guest@rabbitmq:5672/"` JaegerURL url.URL `env:"SMQ_JAEGER_URL" envDefault:"http://localhost:4318/v1/traces"` SendTelemetry bool `env:"SMQ_SEND_TELEMETRY" envDefault:"true"` InstanceID string `env:"SMQ_COAP_ADAPTER_INSTANCE_ID" envDefault:""` TraceRatio float64 `env:"SMQ_JAEGER_TRACE_RATIO" envDefault:"1.0"` - ESURL string `env:"SMQ_ES_URL" envDefault:"nats://localhost:4222"` + ESURL string `env:"SMQ_ES_URL" envDefault:"amqp://guest:guest@rabbitmq:5672/"` } func main() { diff --git a/cmd/domains/main.go b/cmd/domains/main.go index 0a4195380e..36014fc3aa 100644 --- a/cmd/domains/main.go +++ b/cmd/domains/main.go @@ -79,7 +79,7 @@ type config struct { SpicedbSchemaFile string `env:"SMQ_SPICEDB_SCHEMA_FILE" envDefault:"schema.zed"` SpicedbPreSharedKey string `env:"SMQ_SPICEDB_PRE_SHARED_KEY" envDefault:"12345678"` TraceRatio float64 `env:"SMQ_JAEGER_TRACE_RATIO" envDefault:"1.0"` - ESURL string `env:"SMQ_ES_URL" envDefault:"nats://localhost:4222"` + ESURL string `env:"SMQ_ES_URL" envDefault:"amqp://guest:guest@rabbitmq:5672/"` } func main() { diff --git a/cmd/groups/main.go b/cmd/groups/main.go index a4fa60d32c..85ab9975da 100644 --- a/cmd/groups/main.go +++ b/cmd/groups/main.go @@ -78,7 +78,7 @@ type config struct { InstanceID string `env:"SMQ_GROUPS_INSTANCE_ID" envDefault:""` JaegerURL url.URL `env:"SMQ_JAEGER_URL" envDefault:"http://localhost:4318/v1/traces"` SendTelemetry bool `env:"SMQ_SEND_TELEMETRY" envDefault:"true"` - ESURL string `env:"SMQ_ES_URL" envDefault:"nats://localhost:4222"` + ESURL string `env:"SMQ_ES_URL" envDefault:"amqp://guest:guest@rabbitmq:5672/"` ESConsumerName string `env:"SMQ_GROUPS_EVENT_CONSUMER" envDefault:"groups"` TraceRatio float64 `env:"SMQ_JAEGER_TRACE_RATIO" envDefault:"1.0"` SpicedbHost string `env:"SMQ_SPICEDB_HOST" envDefault:"localhost"` diff --git a/cmd/http/main.go b/cmd/http/main.go index 4b03eb2426..4ca040c685 100644 --- a/cmd/http/main.go +++ b/cmd/http/main.go @@ -55,12 +55,12 @@ const ( type config struct { LogLevel string `env:"SMQ_HTTP_ADAPTER_LOG_LEVEL" envDefault:"info"` - BrokerURL string `env:"SMQ_MESSAGE_BROKER_URL" envDefault:"nats://localhost:4222"` + BrokerURL string `env:"SMQ_MESSAGE_BROKER_URL" envDefault:"amqp://guest:guest@rabbitmq:5672/"` JaegerURL url.URL `env:"SMQ_JAEGER_URL" envDefault:"http://localhost:4318/v1/traces"` SendTelemetry bool `env:"SMQ_SEND_TELEMETRY" envDefault:"true"` InstanceID string `env:"SMQ_HTTP_ADAPTER_INSTANCE_ID" envDefault:""` TraceRatio float64 `env:"SMQ_JAEGER_TRACE_RATIO" envDefault:"1.0"` - ESURL string `env:"SMQ_ES_URL" envDefault:"nats://localhost:4222"` + ESURL string `env:"SMQ_ES_URL" envDefault:"amqp://guest:guest@rabbitmq:5672/"` } func main() { diff --git a/cmd/journal/main.go b/cmd/journal/main.go index 4401644c1c..65fd1dfea4 100644 --- a/cmd/journal/main.go +++ b/cmd/journal/main.go @@ -51,7 +51,7 @@ const ( type config struct { LogLevel string `env:"SMQ_JOURNAL_LOG_LEVEL" envDefault:"info"` - ESURL string `env:"SMQ_ES_URL" envDefault:"nats://localhost:4222"` + ESURL string `env:"SMQ_ES_URL" envDefault:"amqp://guest:guest@rabbitmq:5672/"` JaegerURL url.URL `env:"SMQ_JAEGER_URL" envDefault:"http://localhost:4318/v1/traces"` SendTelemetry bool `env:"SMQ_SEND_TELEMETRY" envDefault:"true"` InstanceID string `env:"SMQ_JOURNAL_INSTANCE_ID" envDefault:""` diff --git a/cmd/mqtt/main.go b/cmd/mqtt/main.go index 9fccd4c2d9..e2829dc786 100644 --- a/cmd/mqtt/main.go +++ b/cmd/mqtt/main.go @@ -66,10 +66,10 @@ type config struct { HTTPTargetPath string `env:"SMQ_MQTT_ADAPTER_WS_TARGET_PATH" envDefault:"/mqtt"` Instance string `env:"SMQ_MQTT_ADAPTER_INSTANCE" envDefault:""` JaegerURL url.URL `env:"SMQ_JAEGER_URL" envDefault:"http://localhost:4318/v1/traces"` - BrokerURL string `env:"SMQ_MESSAGE_BROKER_URL" envDefault:"nats://localhost:4222"` + BrokerURL string `env:"SMQ_MESSAGE_BROKER_URL" envDefault:"amqp://guest:guest@rabbitmq:5672/"` SendTelemetry bool `env:"SMQ_SEND_TELEMETRY" envDefault:"true"` InstanceID string `env:"SMQ_MQTT_ADAPTER_INSTANCE_ID" envDefault:""` - ESURL string `env:"SMQ_ES_URL" envDefault:"nats://localhost:4222"` + ESURL string `env:"SMQ_ES_URL" envDefault:"amqp://guest:guest@rabbitmq:5672/"` TraceRatio float64 `env:"SMQ_JAEGER_TRACE_RATIO" envDefault:"1.0"` } diff --git a/cmd/users/main.go b/cmd/users/main.go index 275a477358..2514529a29 100644 --- a/cmd/users/main.go +++ b/cmd/users/main.go @@ -78,7 +78,7 @@ type config struct { JaegerURL url.URL `env:"SMQ_JAEGER_URL" envDefault:"http://localhost:4318/v1/traces"` SendTelemetry bool `env:"SMQ_SEND_TELEMETRY" envDefault:"true"` InstanceID string `env:"SMQ_USERS_INSTANCE_ID" envDefault:""` - ESURL string `env:"SMQ_ES_URL" envDefault:"nats://localhost:4222"` + ESURL string `env:"SMQ_ES_URL" envDefault:"amqp://guest:guest@rabbitmq:5672/"` TraceRatio float64 `env:"SMQ_JAEGER_TRACE_RATIO" envDefault:"1.0"` SelfRegister bool `env:"SMQ_USERS_ALLOW_SELF_REGISTER" envDefault:"false"` OAuthUIRedirectURL string `env:"SMQ_OAUTH_UI_REDIRECT_URL" envDefault:"http://localhost:9095/domains"` diff --git a/cmd/ws/main.go b/cmd/ws/main.go index 8dc5223f92..f83a5ff2b8 100644 --- a/cmd/ws/main.go +++ b/cmd/ws/main.go @@ -51,12 +51,12 @@ const ( type config struct { LogLevel string `env:"SMQ_WS_ADAPTER_LOG_LEVEL" envDefault:"info"` - BrokerURL string `env:"SMQ_MESSAGE_BROKER_URL" envDefault:"nats://localhost:4222"` + BrokerURL string `env:"SMQ_MESSAGE_BROKER_URL" envDefault:"amqp://guest:guest@rabbitmq:5672/"` JaegerURL url.URL `env:"SMQ_JAEGER_URL" envDefault:"http://localhost:4318/v1/traces"` SendTelemetry bool `env:"SMQ_SEND_TELEMETRY" envDefault:"true"` InstanceID string `env:"SMQ_WS_ADAPTER_INSTANCE_ID" envDefault:""` TraceRatio float64 `env:"SMQ_JAEGER_TRACE_RATIO" envDefault:"1.0"` - ESURL string `env:"SMQ_ES_URL" envDefault:"nats://localhost:4222"` + ESURL string `env:"SMQ_ES_URL" envDefault:"amqp://guest:guest@rabbitmq:5672/"` } func main() { diff --git a/coap/README.md b/coap/README.md index 24810dbf5b..eaa3631bf7 100644 --- a/coap/README.md +++ b/coap/README.md @@ -6,27 +6,27 @@ SuperMQ CoAP adapter provides an [CoAP](http://coap.technology/) API for sending The service is configured using the environment variables presented in the following table. Note that any unset variables will be replaced with their default values. -| Variable | Description | Default | -| ---------------------------------- | ----------------------------------------------------------------------------------- | --------------------------------- | -| SMQ_COAP_ADAPTER_LOG_LEVEL | Log level for the CoAP Adapter (debug, info, warn, error) | info | -| SMQ_COAP_ADAPTER_HOST | CoAP service listening host | "" | -| SMQ_COAP_ADAPTER_PORT | CoAP service listening port | 5683 | -| SMQ_COAP_ADAPTER_SERVER_CERT | CoAP service server certificate | "" | -| SMQ_COAP_ADAPTER_SERVER_KEY | CoAP service server key | "" | -| SMQ_COAP_ADAPTER_HTTP_HOST | Service HTTP listening host | "" | -| SMQ_COAP_ADAPTER_HTTP_PORT | Service listening port | 5683 | -| SMQ_COAP_ADAPTER_HTTP_SERVER_CERT | Service server certificate | "" | -| SMQ_COAP_ADAPTER_HTTP_SERVER_KEY | Service server key | "" | -| SMQ_CLIENTS_AUTH_GRPC_URL | Clients service Auth gRPC URL | | -| SMQ_CLIENTS_AUTH_GRPC_TIMEOUT | Clients service Auth gRPC request timeout in seconds | 1s | -| SMQ_CLIENTS_AUTH_GRPC_CLIENT_CERT | Path to the PEM encoded clients service Auth gRPC client certificate file | "" | -| SMQ_CLIENTS_AUTH_GRPC_CLIENT_KEY | Path to the PEM encoded clients service Auth gRPC client key file | "" | -| SMQ_CLIENTS_AUTH_GRPC_SERVER_CERTS | Path to the PEM encoded clients server Auth gRPC server trusted CA certificate file | "" | -| SMQ_MESSAGE_BROKER_URL | Message broker instance URL | | -| SMQ_JAEGER_URL | Jaeger server URL | | -| SMQ_JAEGER_TRACE_RATIO | Jaeger sampling ratio | 1.0 | -| SMQ_SEND_TELEMETRY | Send telemetry to magistrala call home server | true | -| SMQ_COAP_ADAPTER_INSTANCE_ID | CoAP adapter instance ID | "" | +| Variable | Description | Default | +| ---------------------------------- | ----------------------------------------------------------------------------------- | ----------------------------------- | +| SMQ_COAP_ADAPTER_LOG_LEVEL | Log level for the CoAP Adapter (debug, info, warn, error) | info | +| SMQ_COAP_ADAPTER_HOST | CoAP service listening host | "" | +| SMQ_COAP_ADAPTER_PORT | CoAP service listening port | 5683 | +| SMQ_COAP_ADAPTER_SERVER_CERT | CoAP service server certificate | "" | +| SMQ_COAP_ADAPTER_SERVER_KEY | CoAP service server key | "" | +| SMQ_COAP_ADAPTER_HTTP_HOST | Service HTTP listening host | "" | +| SMQ_COAP_ADAPTER_HTTP_PORT | Service listening port | 5683 | +| SMQ_COAP_ADAPTER_HTTP_SERVER_CERT | Service server certificate | "" | +| SMQ_COAP_ADAPTER_HTTP_SERVER_KEY | Service server key | "" | +| SMQ_CLIENTS_AUTH_GRPC_URL | Clients service Auth gRPC URL | | +| SMQ_CLIENTS_AUTH_GRPC_TIMEOUT | Clients service Auth gRPC request timeout in seconds | 1s | +| SMQ_CLIENTS_AUTH_GRPC_CLIENT_CERT | Path to the PEM encoded clients service Auth gRPC client certificate file | "" | +| SMQ_CLIENTS_AUTH_GRPC_CLIENT_KEY | Path to the PEM encoded clients service Auth gRPC client key file | "" | +| SMQ_CLIENTS_AUTH_GRPC_SERVER_CERTS | Path to the PEM encoded clients server Auth gRPC server trusted CA certificate file | "" | +| SMQ_MESSAGE_BROKER_URL | Message broker instance URL | | +| SMQ_JAEGER_URL | Jaeger server URL | | +| SMQ_JAEGER_TRACE_RATIO | Jaeger sampling ratio | 1.0 | +| SMQ_SEND_TELEMETRY | Send telemetry to magistrala call home server | true | +| SMQ_COAP_ADAPTER_INSTANCE_ID | CoAP adapter instance ID | "" | ## Deployment @@ -62,7 +62,7 @@ SMQ_CLIENTS_AUTH_GRPC_TIMEOUT=1s \ SMQ_CLIENTS_AUTH_GRPC_CLIENT_CERT="" \ SMQ_CLIENTS_AUTH_GRPC_CLIENT_KEY="" \ SMQ_CLIENTS_AUTH_GRPC_SERVER_CERTS="" \ -SMQ_MESSAGE_BROKER_URL=nats://localhost:4222 \ +SMQ_MESSAGE_BROKER_URL=amqp://guest:guest@rabbitmq:5672/ \ SMQ_JAEGER_URL=http://localhost:14268/api/traces \ SMQ_JAEGER_TRACE_RATIO=1.0 \ SMQ_SEND_TELEMETRY=true \ diff --git a/docker/.env b/docker/.env index 973a98a348..58ba6a3e0a 100644 --- a/docker/.env +++ b/docker/.env @@ -34,8 +34,8 @@ SMQ_RABBITMQ_MQTT_QOS=2 SMQ_RABBITMQ_WS_TARGET_PATH=/ ## Message Broker -SMQ_MESSAGE_BROKER_TYPE=nats -SMQ_MESSAGE_BROKER_URL=${SMQ_NATS_URL} +SMQ_MESSAGE_BROKER_TYPE=rabbitmq +SMQ_MESSAGE_BROKER_URL=${SMQ_RABBITMQ_URL} ## MQTT Broker SMQ_MQTT_BROKER_TYPE=rabbitmq diff --git a/docker/README.md b/docker/README.md index ecf02df9a4..3f9292a6f6 100644 --- a/docker/README.md +++ b/docker/README.md @@ -26,14 +26,14 @@ To pull docker images from a specific release you need to change the value of `S SuperMQ supports configurable MQTT broker and Message broker, which also acts as an events store. SuperMQ uses two types of brokers: -1. MQTT_BROKER: Handles MQTT communication between MQTT adapters and message broker. This can either be 'RabbitMQ' or 'NATS'. +1. MQTT_BROKER: Handles MQTT communication between MQTT adapters and message broker. This is RabbitMQ 2. MESSAGE_BROKER: Manages message exchange between SuperMQ core, optional, and external services. This can either be 'NATS' or 'RabbitMQ'. This is used to store messages for distributed processing. Events store: This is used by SuperMQ services to store events for distributed processing. SuperMQ uses a single service to be the message broker and events store. This can either be 'NATS' or 'RabbitMQ'. Redis can also be used as an events store, but it requires a message broker to be deployed along with it for message exchange. This is the same as MESSAGE_BROKER. This can either be 'NATS' or 'RabbitMQ' or 'Redis'. If Redis is used as an events store, then RabbitMQ or NATS is used as a message broker. -The current deployment strategy for SuperMQ in `docker/docker-compose.yml` is to use RabbitMQ as a MQTT_BROKER and NATS as a MESSAGE_BROKER and EVENTS_STORE. +The current deployment strategy for SuperMQ in `docker/docker-compose.yml` is to use RabbitMQ as a MQTT_BROKER and RabbitMQ as a MESSAGE_BROKER and EVENTS_STORE. Therefore, the following combinations are possible: @@ -41,31 +41,27 @@ Therefore, the following combinations are possible: - MQTT_BROKER: RabbitMQ, MESSAGE_BROKER: NATS, EVENTS_STORE: Redis - MQTT_BROKER: RabbitMQ, MESSAGE_BROKER: RabbitMQ, EVENTS_STORE: RabbitMQ - MQTT_BROKER: RabbitMQ, MESSAGE_BROKER: RabbitMQ, EVENTS_STORE: Redis -- MQTT_BROKER: NATS, MESSAGE_BROKER: RabbitMQ, EVENTS_STORE: RabbitMQ -- MQTT_BROKER: NATS, MESSAGE_BROKER: RabbitMQ, EVENTS_STORE: Redis -- MQTT_BROKER: NATS, MESSAGE_BROKER: NATS, EVENTS_STORE: NATS -- MQTT_BROKER: NATS, MESSAGE_BROKER: NATS, EVENTS_STORE: Redis -For Message brokers other than NATS, you would need to build the docker images with RabbitMQ as the build tag and change the `docker/.env`. For example, to use RabbitMQ as a message broker: +For Message brokers other than RabbitMQ, you would need to build the docker images with RabbitMQ as the build tag and change the `docker/.env`. For example, to use RabbitMQ as a message broker: ```bash -SMQ_MESSAGE_BROKER_TYPE=rabbitmq make dockers +SMQ_MESSAGE_BROKER_TYPE=nats make dockers ``` ```env -SMQ_MESSAGE_BROKER_TYPE=rabbitmq -SMQ_MESSAGE_BROKER_URL=${SMQ_RABBITMQ_URL} +SMQ_MESSAGE_BROKER_TYPE=nats +SMQ_MESSAGE_BROKER_URL=${SMQ_NATS_URL} ``` For Redis as an events store, you would need to run RabbitMQ or NATS as a message broker. For example, to use Redis as an events store with rabbitmq as a message broker: ```bash -SMQ_ES_TYPE=redis SMQ_MESSAGE_BROKER_TYPE=rabbitmq make dockers +SMQ_ES_TYPE=redis SMQ_MESSAGE_BROKER_TYPE=nats make dockers ``` ```env -SMQ_MESSAGE_BROKER_TYPE=rabbitmq -SMQ_MESSAGE_BROKER_URL=${SMQ_RABBITMQ_URL} +SMQ_MESSAGE_BROKER_TYPE=nats +SMQ_MESSAGE_BROKER_URL=${SMQ_NATS_URL} SMQ_ES_TYPE=redis SMQ_ES_URL=${SMQ_REDIS_URL} ``` diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 9655ce3647..b510acdf9a 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -365,7 +365,7 @@ services: - clients-db - users - auth - - nats + - rabbitmq restart: on-failure environment: SMQ_CLIENTS_LOG_LEVEL: ${SMQ_CLIENTS_LOG_LEVEL} @@ -524,7 +524,7 @@ services: - channels-db - users - auth - - nats + - rabbitmq restart: on-failure environment: SMQ_CHANNELS_LOG_LEVEL: ${SMQ_CHANNELS_LOG_LEVEL} @@ -684,7 +684,7 @@ services: depends_on: - users-db - auth - - nats + - rabbitmq restart: on-failure environment: SMQ_USERS_LOG_LEVEL: ${SMQ_USERS_LOG_LEVEL} @@ -790,7 +790,7 @@ services: depends_on: - groups-db - auth - - nats + - rabbitmq restart: on-failure environment: SMQ_GROUPS_LOG_LEVEL: ${SMQ_GROUPS_LOG_LEVEL} @@ -887,7 +887,6 @@ services: depends_on: - clients - rabbitmq - - nats restart: on-failure environment: SMQ_MQTT_ADAPTER_LOG_LEVEL: ${SMQ_MQTT_ADAPTER_LOG_LEVEL} @@ -961,7 +960,7 @@ services: container_name: supermq-http depends_on: - clients - - nats + - rabbitmq restart: on-failure environment: SMQ_HTTP_ADAPTER_LOG_LEVEL: ${SMQ_HTTP_ADAPTER_LOG_LEVEL} @@ -1049,7 +1048,7 @@ services: container_name: supermq-coap depends_on: - clients - - nats + - rabbitmq restart: on-failure environment: SMQ_COAP_ADAPTER_LOG_LEVEL: ${SMQ_COAP_ADAPTER_LOG_LEVEL} @@ -1126,7 +1125,7 @@ services: container_name: supermq-ws depends_on: - clients - - nats + - rabbitmq restart: on-failure environment: SMQ_WS_ADAPTER_LOG_LEVEL: ${SMQ_WS_ADAPTER_LOG_LEVEL} @@ -1228,21 +1227,3 @@ services: - supermq-mqtt-broker-volume:/var/lib/rabbitmq networks: - supermq-base-net - - nats: - image: nats:2.10.25-alpine - container_name: supermq-nats - restart: on-failure - command: "--config=/etc/nats/nats.conf" - environment: - - SMQ_NATS_PORT=${SMQ_NATS_PORT} - - SMQ_NATS_HTTP_PORT=${SMQ_NATS_HTTP_PORT} - - SMQ_NATS_JETSTREAM_KEY=${SMQ_NATS_JETSTREAM_KEY} - ports: - - ${SMQ_NATS_PORT}:${SMQ_NATS_PORT} - - ${SMQ_NATS_HTTP_PORT}:${SMQ_NATS_HTTP_PORT} - volumes: - - supermq-broker-volume:/data - - ./nats:/etc/nats - networks: - - supermq-base-net diff --git a/http/README.md b/http/README.md index e0be9d0236..8ef497f6b0 100644 --- a/http/README.md +++ b/http/README.md @@ -6,23 +6,23 @@ HTTP adapter provides an HTTP API for sending messages through the platform. The service is configured using the environment variables presented in the following table. Note that any unset variables will be replaced with their default values. -| Variable | Description | Default | -| ---------------------------------- | ----------------------------------------------------------------------------------- | --------------------------------- | -| SMQ_HTTP_ADAPTER_LOG_LEVEL | Log level for the HTTP Adapter (debug, info, warn, error) | info | -| SMQ_HTTP_ADAPTER_HOST | Service HTTP host | "" | -| SMQ_HTTP_ADAPTER_PORT | Service HTTP port | 80 | -| SMQ_HTTP_ADAPTER_SERVER_CERT | Path to the PEM encoded server certificate file | "" | -| SMQ_HTTP_ADAPTER_SERVER_KEY | Path to the PEM encoded server key file | "" | -| SMQ_CLIENTS_AUTH_GRPC_URL | Clients service Auth gRPC URL | | -| SMQ_CLIENTS_AUTH_GRPC_TIMEOUT | Clients service Auth gRPC request timeout in seconds | 1s | -| SMQ_CLIENTS_AUTH_GRPC_CLIENT_CERT | Path to the PEM encoded clients service Auth gRPC client certificate file | "" | -| SMQ_CLIENTS_AUTH_GRPC_CLIENT_KEY | Path to the PEM encoded clients service Auth gRPC client key file | "" | -| SMQ_CLIENTS_AUTH_GRPC_SERVER_CERTS | Path to the PEM encoded clients server Auth gRPC server trusted CA certificate file | "" | -| SMQ_MESSAGE_BROKER_URL | Message broker instance URL | | -| SMQ_JAEGER_URL | Jaeger server URL | | -| SMQ_JAEGER_TRACE_RATIO | Jaeger sampling ratio | 1.0 | -| SMQ_SEND_TELEMETRY | Send telemetry to supermq call home server | true | -| SMQ_HTTP_ADAPTER_INSTANCE_ID | Service instance ID | "" | +| Variable | Description | Default | +| ---------------------------------- | ----------------------------------------------------------------------------------- | ----------------------------------- | +| SMQ_HTTP_ADAPTER_LOG_LEVEL | Log level for the HTTP Adapter (debug, info, warn, error) | info | +| SMQ_HTTP_ADAPTER_HOST | Service HTTP host | "" | +| SMQ_HTTP_ADAPTER_PORT | Service HTTP port | 80 | +| SMQ_HTTP_ADAPTER_SERVER_CERT | Path to the PEM encoded server certificate file | "" | +| SMQ_HTTP_ADAPTER_SERVER_KEY | Path to the PEM encoded server key file | "" | +| SMQ_CLIENTS_AUTH_GRPC_URL | Clients service Auth gRPC URL | | +| SMQ_CLIENTS_AUTH_GRPC_TIMEOUT | Clients service Auth gRPC request timeout in seconds | 1s | +| SMQ_CLIENTS_AUTH_GRPC_CLIENT_CERT | Path to the PEM encoded clients service Auth gRPC client certificate file | "" | +| SMQ_CLIENTS_AUTH_GRPC_CLIENT_KEY | Path to the PEM encoded clients service Auth gRPC client key file | "" | +| SMQ_CLIENTS_AUTH_GRPC_SERVER_CERTS | Path to the PEM encoded clients server Auth gRPC server trusted CA certificate file | "" | +| SMQ_MESSAGE_BROKER_URL | Message broker instance URL | | +| SMQ_JAEGER_URL | Jaeger server URL | | +| SMQ_JAEGER_TRACE_RATIO | Jaeger sampling ratio | 1.0 | +| SMQ_SEND_TELEMETRY | Send telemetry to supermq call home server | true | +| SMQ_HTTP_ADAPTER_INSTANCE_ID | Service instance ID | "" | ## Deployment @@ -54,7 +54,7 @@ SMQ_CLIENTS_AUTH_GRPC_TIMEOUT=1s \ SMQ_CLIENTS_AUTH_GRPC_CLIENT_CERT="" \ SMQ_CLIENTS_AUTH_GRPC_CLIENT_KEY="" \ SMQ_CLIENTS_AUTH_GRPC_SERVER_CERTS="" \ -SMQ_MESSAGE_BROKER_URL=nats://localhost:4222 \ +SMQ_MESSAGE_BROKER_URL=amqp://guest:guest@rabbitmq:5672/ \ SMQ_JAEGER_URL=http://localhost:14268/api/traces \ SMQ_JAEGER_TRACE_RATIO=1.0 \ SMQ_SEND_TELEMETRY=true \ diff --git a/mqtt/README.md b/mqtt/README.md index aaa92c1307..fa34c7d5b5 100644 --- a/mqtt/README.md +++ b/mqtt/README.md @@ -6,31 +6,31 @@ MQTT adapter provides an MQTT API for sending messages through the platform. MQT The service is configured using the environment variables presented in the following table. Note that any unset variables will be replaced with their default values. -| Variable | Description | Default | -| ---------------------------------------- | ----------------------------------------------------------------------------------- | --------------------------------- | -| SMQ_MQTT_ADAPTER_LOG_LEVEL | Log level for the MQTT Adapter (debug, info, warn, error) | info | -| SMQ_MQTT_ADAPTER_MQTT_PORT | mProxy port | 1883 | -| SMQ_MQTT_ADAPTER_MQTT_TARGET_HOST | MQTT broker host | localhost | -| SMQ_MQTT_ADAPTER_MQTT_TARGET_PORT | MQTT broker port | 1883 | -| SMQ_MQTT_ADAPTER_MQTT_QOS | MQTT broker QoS | 1 | -| SMQ_MQTT_ADAPTER_FORWARDER_TIMEOUT | MQTT forwarder for multiprotocol communication timeout | 30s | -| SMQ_MQTT_ADAPTER_MQTT_TARGET_HEALTH_CHECK | URL of broker health check | "" | -| SMQ_MQTT_ADAPTER_WS_PORT | mProxy MQTT over WS port | 8080 | -| SMQ_MQTT_ADAPTER_WS_TARGET_HOST | MQTT broker host for MQTT over WS | localhost | -| SMQ_MQTT_ADAPTER_WS_TARGET_PORT | MQTT broker port for MQTT over WS | 8080 | -| SMQ_MQTT_ADAPTER_WS_TARGET_PATH | MQTT broker MQTT over WS path | /mqtt | -| SMQ_MQTT_ADAPTER_INSTANCE | Instance name for MQTT adapter | "" | -| SMQ_CLIENTS_AUTH_GRPC_URL | Clients service Auth gRPC URL | | -| SMQ_CLIENTS_AUTH_GRPC_TIMEOUT | Clients service Auth gRPC request timeout in seconds | 1s | -| SMQ_CLIENTS_AUTH_GRPC_CLIENT_CERT | Path to the PEM encoded clients service Auth gRPC client certificate file | "" | -| SMQ_CLIENTS_AUTH_GRPC_CLIENT_KEY | Path to the PEM encoded clients service Auth gRPC client key file | "" | -| SMQ_CLIENTS_AUTH_GRPC_SERVER_CERTS | Path to the PEM encoded clients server Auth gRPC server trusted CA certificate file | "" | -| SMQ_ES_URL | Event sourcing URL | | -| SMQ_MESSAGE_BROKER_URL | Message broker instance URL | | -| SMQ_JAEGER_URL | Jaeger server URL | | -| SMQ_JAEGER_TRACE_RATIO | Jaeger sampling ratio | 1.0 | -| SMQ_SEND_TELEMETRY | Send telemetry to supermq call home server | true | -| SMQ_MQTT_ADAPTER_INSTANCE_ID | Service instance ID | "" | +| Variable | Description | Default | +| ----------------------------------------- | ----------------------------------------------------------------------------------- | ----------------------------------- | +| SMQ_MQTT_ADAPTER_LOG_LEVEL | Log level for the MQTT Adapter (debug, info, warn, error) | info | +| SMQ_MQTT_ADAPTER_MQTT_PORT | mProxy port | 1883 | +| SMQ_MQTT_ADAPTER_MQTT_TARGET_HOST | MQTT broker host | localhost | +| SMQ_MQTT_ADAPTER_MQTT_TARGET_PORT | MQTT broker port | 1883 | +| SMQ_MQTT_ADAPTER_MQTT_QOS | MQTT broker QoS | 1 | +| SMQ_MQTT_ADAPTER_FORWARDER_TIMEOUT | MQTT forwarder for multiprotocol communication timeout | 30s | +| SMQ_MQTT_ADAPTER_MQTT_TARGET_HEALTH_CHECK | URL of broker health check | "" | +| SMQ_MQTT_ADAPTER_WS_PORT | mProxy MQTT over WS port | 8080 | +| SMQ_MQTT_ADAPTER_WS_TARGET_HOST | MQTT broker host for MQTT over WS | localhost | +| SMQ_MQTT_ADAPTER_WS_TARGET_PORT | MQTT broker port for MQTT over WS | 8080 | +| SMQ_MQTT_ADAPTER_WS_TARGET_PATH | MQTT broker MQTT over WS path | /mqtt | +| SMQ_MQTT_ADAPTER_INSTANCE | Instance name for MQTT adapter | "" | +| SMQ_CLIENTS_AUTH_GRPC_URL | Clients service Auth gRPC URL | | +| SMQ_CLIENTS_AUTH_GRPC_TIMEOUT | Clients service Auth gRPC request timeout in seconds | 1s | +| SMQ_CLIENTS_AUTH_GRPC_CLIENT_CERT | Path to the PEM encoded clients service Auth gRPC client certificate file | "" | +| SMQ_CLIENTS_AUTH_GRPC_CLIENT_KEY | Path to the PEM encoded clients service Auth gRPC client key file | "" | +| SMQ_CLIENTS_AUTH_GRPC_SERVER_CERTS | Path to the PEM encoded clients server Auth gRPC server trusted CA certificate file | "" | +| SMQ_ES_URL | Event sourcing URL | | +| SMQ_MESSAGE_BROKER_URL | Message broker instance URL | | +| SMQ_JAEGER_URL | Jaeger server URL | | +| SMQ_JAEGER_TRACE_RATIO | Jaeger sampling ratio | 1.0 | +| SMQ_SEND_TELEMETRY | Send telemetry to supermq call home server | true | +| SMQ_MQTT_ADAPTER_INSTANCE_ID | Service instance ID | "" | ## Deployment @@ -69,8 +69,8 @@ SMQ_CLIENTS_AUTH_GRPC_TIMEOUT=1s \ SMQ_CLIENTS_AUTH_GRPC_CLIENT_CERT="" \ SMQ_CLIENTS_AUTH_GRPC_CLIENT_KEY="" \ SMQ_CLIENTS_AUTH_GRPC_SERVER_CERTS="" \ -SMQ_ES_URL=nats://localhost:4222 \ -SMQ_MESSAGE_BROKER_URL=nats://localhost:4222 \ +SMQ_ES_URL=amqp://guest:guest@rabbitmq:5672/ \ +SMQ_MESSAGE_BROKER_URL=amqp://guest:guest@rabbitmq:5672/ \ SMQ_JAEGER_URL=http://localhost:14268/api/traces \ SMQ_JAEGER_TRACE_RATIO=1.0 \ SMQ_SEND_TELEMETRY=true \ diff --git a/pkg/messaging/README.md b/pkg/messaging/README.md index f8b07f8eb1..6c5e8a042b 100644 --- a/pkg/messaging/README.md +++ b/pkg/messaging/README.md @@ -1,8 +1,8 @@ # Messaging -`messaging` package defines `Publisher`, `Subscriber` and an aggregate `Pubsub` interface. +`messaging` package defines `Publisher`, `Subscriber` and an aggregate `Pubsub` interface. -`Subscriber` interface defines methods used to subscribe to a message broker such as MQTT or NATS or RabbitMQ. +`Subscriber` interface defines methods used to subscribe to a message broker such as MQTT or NATS or RabbitMQ. `Publisher` interface defines methods used to publish messages to a message broker such as MQTT or NATS or RabbitMQ. diff --git a/pkg/messaging/brokers/brokers_nats.go b/pkg/messaging/brokers/brokers_nats.go index 2a7186e3ec..9fa57379ae 100644 --- a/pkg/messaging/brokers/brokers_nats.go +++ b/pkg/messaging/brokers/brokers_nats.go @@ -1,8 +1,8 @@ // Copyright (c) Abstract Machines // SPDX-License-Identifier: Apache-2.0 -//go:build !rabbitmq -// +build !rabbitmq +//go:build nats +// +build nats package brokers diff --git a/pkg/messaging/brokers/brokers_rabbitmq.go b/pkg/messaging/brokers/brokers_rabbitmq.go index 8c3b7dd5f7..c339cb246a 100644 --- a/pkg/messaging/brokers/brokers_rabbitmq.go +++ b/pkg/messaging/brokers/brokers_rabbitmq.go @@ -1,8 +1,8 @@ // Copyright (c) Abstract Machines // SPDX-License-Identifier: Apache-2.0 -//go:build rabbitmq -// +build rabbitmq +//go:build !nats +// +build !nats package brokers diff --git a/pkg/messaging/brokers/tracing/brokers_nats.go b/pkg/messaging/brokers/tracing/brokers_nats.go index 9bca4a6a72..3693db0e21 100644 --- a/pkg/messaging/brokers/tracing/brokers_nats.go +++ b/pkg/messaging/brokers/tracing/brokers_nats.go @@ -1,8 +1,8 @@ // Copyright (c) Abstract Machines // SPDX-License-Identifier: Apache-2.0 -//go:build !rabbitmq -// +build !rabbitmq +//go:build nats +// +build nats package brokers diff --git a/pkg/messaging/brokers/tracing/brokers_rabbitmq.go b/pkg/messaging/brokers/tracing/brokers_rabbitmq.go index 90d4567cc8..d67685d0d3 100644 --- a/pkg/messaging/brokers/tracing/brokers_rabbitmq.go +++ b/pkg/messaging/brokers/tracing/brokers_rabbitmq.go @@ -1,5 +1,5 @@ -//go:build rabbitmq -// +build rabbitmq +//go:build !nats +// +build !nats // Copyright (c) Abstract Machines // SPDX-License-Identifier: Apache-2.0 diff --git a/tools/config/golangci.yml b/tools/config/golangci.yml index 8c1b5f0177..1033af94a1 100644 --- a/tools/config/golangci.yml +++ b/tools/config/golangci.yml @@ -4,7 +4,7 @@ run: timeout: 10m build-tags: - - "nats" + - "rabbitmq" issues: max-issues-per-linter: 100 diff --git a/users/README.md b/users/README.md index 4db609d4d8..f1557eea4e 100644 --- a/users/README.md +++ b/users/README.md @@ -12,49 +12,49 @@ For in-depth explanation of the aforementioned scenarios, as well as thorough un The service is configured using the environment variables presented in the following table. Note that any unset variables will be replaced with their default values. -| Variable | Description | Default | -| ------------------------------ | ----------------------------------------------------------------------- | --------------------------------- | -| SMQ_USERS_LOG_LEVEL | Log level for users service (debug, info, warn, error) | info | -| SMQ_USERS_ADMIN_EMAIL | Default user, created on startup | | -| SMQ_USERS_ADMIN_PASSWORD | Default user password, created on startup | 12345678 | -| SMQ_USERS_PASS_REGEX | Password regex | ^.{8,}$ | -| SMQ_TOKEN_RESET_ENDPOINT | Password request reset endpoint, for constructing link | /reset-request | -| SMQ_USERS_HTTP_HOST | Users service HTTP host | localhost | -| SMQ_USERS_HTTP_PORT | Users service HTTP port | 9002 | -| SMQ_USERS_HTTP_SERVER_CERT | Path to the PEM encoded server certificate file | "" | -| SMQ_USERS_HTTP_SERVER_KEY | Path to the PEM encoded server key file | "" | -| SMQ_USERS_HTTP_SERVER_CA_CERTS | Path to the PEM encoded server CA certificate file | "" | -| SMQ_USERS_HTTP_CLIENT_CA_CERTS | Path to the PEM encoded client CA certificate file | "" | -| SMQ_AUTH_GRPC_URL | Auth service GRPC URL | localhost:8181 | -| SMQ_AUTH_GRPC_TIMEOUT | Auth service GRPC timeout | 1s | -| SMQ_AUTH_GRPC_CLIENT_CERT | Path to the PEM encoded client certificate file | "" | -| SMQ_AUTH_GRPC_CLIENT_KEY | Path to the PEM encoded client key file | "" | -| SMQ_AUTH_GRPC_SERVER_CA_CERTS | Path to the PEM encoded server CA certificate file | "" | -| SMQ_USERS_DB_HOST | Database host address | localhost | -| SMQ_USERS_DB_PORT | Database host port | 5432 | -| SMQ_USERS_DB_USER | Database user | supermq | -| SMQ_USERS_DB_PASS | Database password | supermq | -| SMQ_USERS_DB_NAME | Name of the database used by the service | users | -| SMQ_USERS_DB_SSL_MODE | Database connection SSL mode (disable, require, verify-ca, verify-full) | disable | -| SMQ_USERS_DB_SSL_CERT | Path to the PEM encoded certificate file | "" | -| SMQ_USERS_DB_SSL_KEY | Path to the PEM encoded key file | "" | -| SMQ_USERS_DB_SSL_ROOT_CERT | Path to the PEM encoded root certificate file | "" | -| SMQ_EMAIL_HOST | Mail server host | localhost | -| SMQ_EMAIL_PORT | Mail server port | 25 | -| SMQ_EMAIL_USERNAME | Mail server username | "" | -| SMQ_EMAIL_PASSWORD | Mail server password | "" | -| SMQ_EMAIL_FROM_ADDRESS | Email "from" address | "" | -| SMQ_EMAIL_FROM_NAME | Email "from" name | "" | -| SMQ_EMAIL_TEMPLATE | Email template for sending emails with password reset link | email.tmpl | -| SMQ_USERS_ES_URL | Event store URL | | -| SMQ_JAEGER_URL | Jaeger server URL | | -| SMQ_OAUTH_UI_REDIRECT_URL | OAuth UI redirect URL | | -| SMQ_OAUTH_UI_ERROR_URL | OAuth UI error URL | | -| SMQ_USERS_DELETE_INTERVAL | Interval for deleting users | 24h | -| SMQ_USERS_DELETE_AFTER | Time after which users are deleted | 720h | -| SMQ_JAEGER_TRACE_RATIO | Jaeger sampling ratio | 1.0 | -| SMQ_SEND_TELEMETRY | Send telemetry to supermq call home server. | true | -| SMQ_USERS_INSTANCE_ID | SuperMQ instance ID | "" | +| Variable | Description | Default | +| ------------------------------ | ----------------------------------------------------------------------- | ----------------------------------- | +| SMQ_USERS_LOG_LEVEL | Log level for users service (debug, info, warn, error) | info | +| SMQ_USERS_ADMIN_EMAIL | Default user, created on startup | | +| SMQ_USERS_ADMIN_PASSWORD | Default user password, created on startup | 12345678 | +| SMQ_USERS_PASS_REGEX | Password regex | ^.{8,}$ | +| SMQ_TOKEN_RESET_ENDPOINT | Password request reset endpoint, for constructing link | /reset-request | +| SMQ_USERS_HTTP_HOST | Users service HTTP host | localhost | +| SMQ_USERS_HTTP_PORT | Users service HTTP port | 9002 | +| SMQ_USERS_HTTP_SERVER_CERT | Path to the PEM encoded server certificate file | "" | +| SMQ_USERS_HTTP_SERVER_KEY | Path to the PEM encoded server key file | "" | +| SMQ_USERS_HTTP_SERVER_CA_CERTS | Path to the PEM encoded server CA certificate file | "" | +| SMQ_USERS_HTTP_CLIENT_CA_CERTS | Path to the PEM encoded client CA certificate file | "" | +| SMQ_AUTH_GRPC_URL | Auth service GRPC URL | localhost:8181 | +| SMQ_AUTH_GRPC_TIMEOUT | Auth service GRPC timeout | 1s | +| SMQ_AUTH_GRPC_CLIENT_CERT | Path to the PEM encoded client certificate file | "" | +| SMQ_AUTH_GRPC_CLIENT_KEY | Path to the PEM encoded client key file | "" | +| SMQ_AUTH_GRPC_SERVER_CA_CERTS | Path to the PEM encoded server CA certificate file | "" | +| SMQ_USERS_DB_HOST | Database host address | localhost | +| SMQ_USERS_DB_PORT | Database host port | 5432 | +| SMQ_USERS_DB_USER | Database user | supermq | +| SMQ_USERS_DB_PASS | Database password | supermq | +| SMQ_USERS_DB_NAME | Name of the database used by the service | users | +| SMQ_USERS_DB_SSL_MODE | Database connection SSL mode (disable, require, verify-ca, verify-full) | disable | +| SMQ_USERS_DB_SSL_CERT | Path to the PEM encoded certificate file | "" | +| SMQ_USERS_DB_SSL_KEY | Path to the PEM encoded key file | "" | +| SMQ_USERS_DB_SSL_ROOT_CERT | Path to the PEM encoded root certificate file | "" | +| SMQ_EMAIL_HOST | Mail server host | localhost | +| SMQ_EMAIL_PORT | Mail server port | 25 | +| SMQ_EMAIL_USERNAME | Mail server username | "" | +| SMQ_EMAIL_PASSWORD | Mail server password | "" | +| SMQ_EMAIL_FROM_ADDRESS | Email "from" address | "" | +| SMQ_EMAIL_FROM_NAME | Email "from" name | "" | +| SMQ_EMAIL_TEMPLATE | Email template for sending emails with password reset link | email.tmpl | +| SMQ_USERS_ES_URL | Event store URL | | +| SMQ_JAEGER_URL | Jaeger server URL | | +| SMQ_OAUTH_UI_REDIRECT_URL | OAuth UI redirect URL | | +| SMQ_OAUTH_UI_ERROR_URL | OAuth UI error URL | | +| SMQ_USERS_DELETE_INTERVAL | Interval for deleting users | 24h | +| SMQ_USERS_DELETE_AFTER | Time after which users are deleted | 720h | +| SMQ_JAEGER_TRACE_RATIO | Jaeger sampling ratio | 1.0 | +| SMQ_SEND_TELEMETRY | Send telemetry to supermq call home server. | true | +| SMQ_USERS_INSTANCE_ID | SuperMQ instance ID | "" | ## Deployment @@ -107,7 +107,7 @@ SMQ_EMAIL_PASSWORD="2b0d302e775b1e" \ SMQ_EMAIL_FROM_ADDRESS=from@example.com \ SMQ_EMAIL_FROM_NAME=Example \ SMQ_EMAIL_TEMPLATE="docker/templates/users.tmpl" \ -SMQ_USERS_ES_URL=nats://localhost:4222 \ +SMQ_USERS_ES_URL=amqp://guest:guest@rabbitmq:5672/ \ SMQ_JAEGER_URL=http://localhost:14268/api/traces \ SMQ_JAEGER_TRACE_RATIO=1.0 \ SMQ_SEND_TELEMETRY=true \ diff --git a/ws/README.md b/ws/README.md index d50f413ba1..28f686edd9 100644 --- a/ws/README.md +++ b/ws/README.md @@ -6,23 +6,23 @@ WebSocket adapter provides a [WebSocket](https://en.wikipedia.org/wiki/WebSocket The service is configured using the environment variables presented in the following table. Note that any unset variables will be replaced with their default values. -| Variable | Description | Default | -| ---------------------------------- | ----------------------------------------------------------------------------------- | --------------------------------- | -| SMQ_WS_ADAPTER_LOG_LEVEL | Log level for the WS Adapter (debug, info, warn, error) | info | -| SMQ_WS_ADAPTER_HTTP_HOST | Service WS host | "" | -| SMQ_WS_ADAPTER_HTTP_PORT | Service WS port | 8190 | -| SMQ_WS_ADAPTER_HTTP_SERVER_CERT | Path to the PEM encoded server certificate file | "" | -| SMQ_WS_ADAPTER_HTTP_SERVER_KEY | Path to the PEM encoded server key file | "" | -| SMQ_CLIENTS_AUTH_GRPC_URL | Clients service Auth gRPC URL | | -| SMQ_CLIENTS_AUTH_GRPC_TIMEOUT | Clients service Auth gRPC request timeout in seconds | 1s | -| SMQ_CLIENTS_AUTH_GRPC_CLIENT_CERT | Path to the PEM encoded clients service Auth gRPC client certificate file | "" | -| SMQ_CLIENTS_AUTH_GRPC_CLIENT_KEY | Path to the PEM encoded clients service Auth gRPC client key file | "" | -| SMQ_CLIENTS_AUTH_GRPC_SERVER_CERTS | Path to the PEM encoded clients server Auth gRPC server trusted CA certificate file | "" | -| SMQ_MESSAGE_BROKER_URL | Message broker instance URL | | -| SMQ_JAEGER_URL | Jaeger server URL | | -| SMQ_JAEGER_TRACE_RATIO | Jaeger sampling ratio | 1.0 | -| SMQ_SEND_TELEMETRY | Send telemetry to supermq call home server | true | -| SMQ_WS_ADAPTER_INSTANCE_ID | Service instance ID | "" | +| Variable | Description | Default | +| ---------------------------------- | ----------------------------------------------------------------------------------- | ----------------------------------- | +| SMQ_WS_ADAPTER_LOG_LEVEL | Log level for the WS Adapter (debug, info, warn, error) | info | +| SMQ_WS_ADAPTER_HTTP_HOST | Service WS host | "" | +| SMQ_WS_ADAPTER_HTTP_PORT | Service WS port | 8190 | +| SMQ_WS_ADAPTER_HTTP_SERVER_CERT | Path to the PEM encoded server certificate file | "" | +| SMQ_WS_ADAPTER_HTTP_SERVER_KEY | Path to the PEM encoded server key file | "" | +| SMQ_CLIENTS_AUTH_GRPC_URL | Clients service Auth gRPC URL | | +| SMQ_CLIENTS_AUTH_GRPC_TIMEOUT | Clients service Auth gRPC request timeout in seconds | 1s | +| SMQ_CLIENTS_AUTH_GRPC_CLIENT_CERT | Path to the PEM encoded clients service Auth gRPC client certificate file | "" | +| SMQ_CLIENTS_AUTH_GRPC_CLIENT_KEY | Path to the PEM encoded clients service Auth gRPC client key file | "" | +| SMQ_CLIENTS_AUTH_GRPC_SERVER_CERTS | Path to the PEM encoded clients server Auth gRPC server trusted CA certificate file | "" | +| SMQ_MESSAGE_BROKER_URL | Message broker instance URL | | +| SMQ_JAEGER_URL | Jaeger server URL | | +| SMQ_JAEGER_TRACE_RATIO | Jaeger sampling ratio | 1.0 | +| SMQ_SEND_TELEMETRY | Send telemetry to supermq call home server | true | +| SMQ_WS_ADAPTER_INSTANCE_ID | Service instance ID | "" | ## Deployment @@ -54,7 +54,7 @@ SMQ_CLIENTS_AUTH_GRPC_TIMEOUT=1s \ SMQ_CLIENTS_AUTH_GRPC_CLIENT_CERT="" \ SMQ_CLIENTS_AUTH_GRPC_CLIENT_KEY="" \ SMQ_CLIENTS_AUTH_GRPC_SERVER_CERTS="" \ -SMQ_MESSAGE_BROKER_URL=nats://localhost:4222 \ +SMQ_MESSAGE_BROKER_URL=amqp://guest:guest@rabbitmq:5672/ \ SMQ_JAEGER_URL=http://localhost:14268/api/traces \ SMQ_JAEGER_TRACE_RATIO=1.0 \ SMQ_SEND_TELEMETRY=true \ From d12c6281680c015e46d4f33c199d2b4c11468821 Mon Sep 17 00:00:00 2001 From: Rodney Osodo Date: Wed, 12 Feb 2025 14:12:57 +0300 Subject: [PATCH 12/13] fix(linter): move copyright above build tags Signed-off-by: Rodney Osodo --- pkg/messaging/brokers/tracing/brokers_rabbitmq.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/messaging/brokers/tracing/brokers_rabbitmq.go b/pkg/messaging/brokers/tracing/brokers_rabbitmq.go index d67685d0d3..c914cc091d 100644 --- a/pkg/messaging/brokers/tracing/brokers_rabbitmq.go +++ b/pkg/messaging/brokers/tracing/brokers_rabbitmq.go @@ -1,9 +1,9 @@ -//go:build !nats -// +build !nats - // Copyright (c) Abstract Machines // SPDX-License-Identifier: Apache-2.0 +//go:build !nats +// +build !nats + package brokers import ( From ddc40fd7c1315e289d4f7a7e1ad8167f645c06f0 Mon Sep 17 00:00:00 2001 From: Rodney Osodo Date: Tue, 11 Feb 2025 15:04:16 +0300 Subject: [PATCH 13/13] fix(websocket): authorization was not working for clients Signed-off-by: Rodney Osodo --- ws/adapter.go | 4 ++ ws/handler.go | 115 ++++++++++++++------------------------------------ 2 files changed, 36 insertions(+), 83 deletions(-) diff --git a/ws/adapter.go b/ws/adapter.go index 02c4cfe39e..0517cbbe83 100644 --- a/ws/adapter.go +++ b/ws/adapter.go @@ -6,6 +6,7 @@ package ws import ( "context" "fmt" + "strings" grpcChannelsV1 "github.com/absmach/supermq/api/grpc/channels/v1" grpcClientsV1 "github.com/absmach/supermq/api/grpc/clients/v1" @@ -93,6 +94,9 @@ func (svc *adapterService) authorize(ctx context.Context, clientKey, chanID stri authnReq := &grpcClientsV1.AuthnReq{ ClientSecret: clientKey, } + if strings.HasPrefix(clientKey, "Client") { + authnReq.ClientSecret = extractClientSecret(clientKey) + } authnRes, err := svc.clients.Authenticate(ctx, authnReq) if err != nil { return "", errors.Wrap(svcerr.ErrAuthentication, err) diff --git a/ws/handler.go b/ws/handler.go index 238011b28d..422e6fec4f 100644 --- a/ws/handler.go +++ b/ws/handler.go @@ -97,7 +97,9 @@ func (h *handler) AuthPublish(ctx context.Context, topic *string, payload *[]byt token = string(s.Password) } - return h.authAccess(ctx, token, *topic, connections.Publish) + _, _, err := h.authAccess(ctx, token, *topic, connections.Publish) + + return err } // AuthSubscribe is called on device publish, @@ -111,16 +113,8 @@ func (h *handler) AuthSubscribe(ctx context.Context, topics *[]string) error { return errMissingTopicSub } - var token string - switch { - case strings.HasPrefix(string(s.Password), "Client"): - token = strings.ReplaceAll(string(s.Password), "Client ", "") - default: - token = string(s.Password) - } - for _, topic := range *topics { - if err := h.authAccess(ctx, token, topic, connections.Subscribe); err != nil { + if _, _, err := h.authAccess(ctx, string(s.Password), topic, connections.Subscribe); err != nil { return err } } @@ -139,7 +133,6 @@ func (h *handler) Publish(ctx context.Context, topic *string, payload *[]byte) e if !ok { return errors.Wrap(errFailedPublish, errClientNotInitialized) } - h.logger.Info(fmt.Sprintf(LogInfoPublished, s.ID, *topic)) if len(*payload) == 0 { return errFailedMessagePublish @@ -160,41 +153,9 @@ func (h *handler) Publish(ctx context.Context, topic *string, payload *[]byte) e return errors.Wrap(errFailedParseSubtopic, err) } - var clientID, clientType string - switch { - case strings.HasPrefix(string(s.Password), "Client"): - clientKey := extractClientSecret(string(s.Password)) - authnRes, err := h.clients.Authenticate(ctx, &grpcClientsV1.AuthnReq{ClientSecret: clientKey}) - if err != nil { - return errors.Wrap(svcerr.ErrAuthentication, err) - } - if !authnRes.Authenticated { - return svcerr.ErrAuthentication - } - clientType = policies.ClientType - clientID = authnRes.GetId() - default: - token := string(s.Password) - authnSession, err := h.authn.Authenticate(ctx, extractBearerToken(token)) - if err != nil { - return err - } - clientType = policies.UserType - clientID = authnSession.DomainUserID - } - - ar := &grpcChannelsV1.AuthzReq{ - Type: uint32(connections.Publish), - ClientId: clientID, - ClientType: clientType, - ChannelId: chanID, - } - res, err := h.channels.Authorize(ctx, ar) + clientID, clientType, err := h.authAccess(ctx, string(s.Password), *topic, connections.Publish) if err != nil { - return err - } - if !res.GetAuthorized() { - return svcerr.ErrAuthorization + return errors.Wrap(errFailedPublish, err) } msg := messaging.Message{ @@ -213,6 +174,8 @@ func (h *handler) Publish(ctx context.Context, topic *string, payload *[]byte) e return errors.Wrap(errFailedPublishToMsgBroker, err) } + h.logger.Info(fmt.Sprintf(LogInfoPublished, s.ID, *topic)) + return nil } @@ -242,38 +205,33 @@ func (h *handler) Disconnect(ctx context.Context) error { return nil } -func (h *handler) authAccess(ctx context.Context, token, topic string, msgType connections.ConnType) error { - var clientID, clientType string - switch { - case strings.HasPrefix(token, "Client"): - clientKey := extractClientSecret(token) - authnRes, err := h.clients.Authenticate(ctx, &grpcClientsV1.AuthnReq{ClientSecret: clientKey}) - if err != nil { - return errors.Wrap(svcerr.ErrAuthentication, err) - } - if !authnRes.Authenticated { - return svcerr.ErrAuthentication - } - clientType = policies.ClientType - clientID = authnRes.GetId() - default: - authnSession, err := h.authn.Authenticate(ctx, extractBearerToken(token)) - if err != nil { - return err - } - clientType = policies.UserType - clientID = authnSession.DomainUserID +func (h *handler) authAccess(ctx context.Context, token, topic string, msgType connections.ConnType) (string, string, error) { + authnReq := &grpcClientsV1.AuthnReq{ + ClientSecret: token, } + if strings.HasPrefix(token, "Client") { + authnReq.ClientSecret = extractClientSecret(token) + } + + authnRes, err := h.clients.Authenticate(ctx, authnReq) + if err != nil { + return "", "", errors.Wrap(svcerr.ErrAuthentication, err) + } + if !authnRes.GetAuthenticated() { + return "", "", svcerr.ErrAuthentication + } + clientType := policies.ClientType + clientID := authnRes.GetId() // Topics are in the format: // channels//messages//.../ct/ if !channelRegExp.MatchString(topic) { - return errMalformedTopic + return "", "", errMalformedTopic } channelParts := channelRegExp.FindStringSubmatch(topic) if len(channelParts) < 1 { - return errMalformedTopic + return "", "", errMalformedTopic } chanID := channelParts[1] @@ -286,13 +244,13 @@ func (h *handler) authAccess(ctx context.Context, token, topic string, msgType c } res, err := h.channels.Authorize(ctx, ar) if err != nil { - return errors.Wrap(svcerr.ErrAuthorization, err) + return "", "", errors.Wrap(svcerr.ErrAuthorization, err) } if !res.GetAuthorized() { - return errors.Wrap(svcerr.ErrAuthorization, err) + return "", "", errors.Wrap(svcerr.ErrAuthorization, err) } - return nil + return clientID, clientType, nil } func parseSubtopic(subtopic string) (string, error) { @@ -325,19 +283,10 @@ func parseSubtopic(subtopic string) (string, error) { } // extractClientSecret returns value of the client secret. If there is no client key - an empty value is returned. -func extractClientSecret(topic string) string { - if !strings.HasPrefix(topic, apiutil.ClientPrefix) { - return "" - } - - return strings.TrimPrefix(topic, apiutil.ClientPrefix) -} - -// extractBearerToken returns value of the bearer token. If there is no bearer token - an empty value is returned. -func extractBearerToken(token string) string { - if !strings.HasPrefix(token, apiutil.BearerPrefix) { +func extractClientSecret(token string) string { + if !strings.HasPrefix(token, apiutil.ClientPrefix) { return "" } - return strings.TrimPrefix(token, apiutil.BearerPrefix) + return strings.TrimPrefix(token, apiutil.ClientPrefix) }