From a27844b3f0218b834990178360b5310c60764ee2 Mon Sep 17 00:00:00 2001 From: ks6088ts Date: Tue, 22 Oct 2024 12:43:57 +0900 Subject: [PATCH] run event grid scripts to communicate via MQTT broker of both mosquitto and Event Grid --- .gitignore | 3 + Makefile | 4 + configs/mosquitto/tls.conf | 12 ++ docs/assets/2_architecture.png | Bin 0 -> 56570 bytes .../scenarios/2_azure_event_grid_messaging.md | 89 +++++++++++ eventgrid.env.template | 12 ++ mkdocs.yml | 1 + scripts/event_grid.py | 143 ++++++++++++++++++ 8 files changed, 264 insertions(+) create mode 100644 configs/mosquitto/tls.conf create mode 100644 docs/assets/2_architecture.png create mode 100644 docs/scenarios/2_azure_event_grid_messaging.md create mode 100644 eventgrid.env.template create mode 100644 scripts/event_grid.py diff --git a/.gitignore b/.gitignore index 899e077..a55c35b 100644 --- a/.gitignore +++ b/.gitignore @@ -162,3 +162,6 @@ cython_debug/ # Project *.env requirements.txt +*.pem +*.crt +*.key diff --git a/Makefile b/Makefile index 1bda522..c6660c9 100644 --- a/Makefile +++ b/Makefile @@ -132,3 +132,7 @@ server: ## run server .PHONY: env env: ## create env files @sh scripts/create_env_files.sh + +.PHONY: mosquitto +mosquitto: ## run mosquitto + cd configs/mosquitto && mosquitto -c tls.conf diff --git a/configs/mosquitto/tls.conf b/configs/mosquitto/tls.conf new file mode 100644 index 0000000..dd2ae59 --- /dev/null +++ b/configs/mosquitto/tls.conf @@ -0,0 +1,12 @@ +per_listener_settings true + +listener 1883 +allow_anonymous true + +listener 8883 +allow_anonymous true +require_certificate true +cafile chain.pem +certfile localhost.crt +keyfile localhost.key +tls_version tlsv1.2 diff --git a/docs/assets/2_architecture.png b/docs/assets/2_architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..ebcf5583a12c5ebd4b2988938c39541b04180efd GIT binary patch literal 56570 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%0A4iU_IZ0%JvI;uQ0<@u`bjNX8cx&iX5J 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 F{|C+Gk;4E0 literal 0 HcmV?d00001 diff --git a/docs/scenarios/2_azure_event_grid_messaging.md b/docs/scenarios/2_azure_event_grid_messaging.md new file mode 100644 index 0000000..603e8de --- /dev/null +++ b/docs/scenarios/2_azure_event_grid_messaging.md @@ -0,0 +1,89 @@ +# Azure Event Grid Messaging + +This scenario demonstrates how to handle messages from Azure Event Grid. + +## Architecture + +[![architecture](../assets/2_architecture.png)](../assets/2_architecture.png) + +## Setup + +Refer to [Quickstart: Publish and subscribe to MQTT messages on Event Grid Namespace with Azure portal](https://learn.microsoft.com/en-us/azure/event-grid/mqtt-publish-and-subscribe-portal) to create an Event Grid Namespace and a topic. + +[Azure-Samples/MqttApplicationSamples](https://github.com/Azure-Samples/MqttApplicationSamples) provides a sample application to publish and subscribe messages to the Event Grid. + +### Create CA certificate and key + +```shell +step ca init \ + --deployment-type standalone \ + --name MqttAppSamplesCA \ + --dns localhost \ + --address 127.0.0.1:443 \ + --provisioner MqttAppSamplesCAProvisioner +``` + +### Create client certificate and key + +```shell +CLIENT_DIR=configs/clients +mkdir -p $CLIENT_DIR + +# Create client certificate and key +CLIENT_NAME=client1 +step certificate create $CLIENT_NAME $CLIENT_DIR/$CLIENT_NAME.pem $CLIENT_DIR/$CLIENT_NAME.key \ + --ca ~/.step/certs/intermediate_ca.crt \ + --ca-key ~/.step/secrets/intermediate_ca_key \ + --no-password \ + --insecure \ + --not-after 2400h + +# Display certificate fingerprint to register the client on Azure Event Grid Namespace +step certificate fingerprint $CLIENT_DIR/$CLIENT_NAME.pem +``` + +### Create mosquitto configuration + +```shell +MOSQUITTO_DIR=configs/mosquitto +mkdir -p $MOSQUITTO_DIR + +# Set up mosquitto +cat ~/.step/certs/root_ca.crt ~/.step/certs/intermediate_ca.crt > $MOSQUITTO_DIR/chain.pem + +step certificate create localhost $MOSQUITTO_DIR/localhost.crt $MOSQUITTO_DIR/localhost.key \ + --ca ~/.step/certs/intermediate_ca.crt \ + --ca-key ~/.step/secrets/intermediate_ca_key \ + --no-password \ + --insecure \ + --not-after 2400h + +# Run mosquitto +make mosquitto +``` + +## Demo + +```shell +# If you use localhost, run mosquitto first +make mosquitto + +# Set the host name of the Event Grid Namespace. If you use localhost, set it to localhost. +HOST_NAME=localhost +# HOST_NAME="EVENT_GRID_NAME.japaneast-1.ts.eventgrid.azure.net" + +# Subscribe the topic +poetry run python scripts/event_grid.py subscribe \ + --topic "sample/topic1" \ + --client-name client1 \ + --host-name $HOST_NAME \ + --verbose + +# Publish messages to the topic +poetry run python scripts/event_grid.py publish \ + --topic "sample/topic1" \ + --payload "helloworld" \ + --client-name client2 \ + --host-name $HOST_NAME \ + --verbose +``` diff --git a/eventgrid.env.template b/eventgrid.env.template new file mode 100644 index 0000000..a74a2de --- /dev/null +++ b/eventgrid.env.template @@ -0,0 +1,12 @@ +MQTT_HOST_NAME="localhost" or "EVENT_GRID_NAMESPACE_NAME.japaneast-1.ts.eventgrid.azure.net" +MQTT_TCP_PORT="8883" +MQTT_USE_TLS="true" +MQTT_CLEAN_SESSION="true" +MQTT_KEEP_ALIVE_IN_SECONDS="30" +MQTT_CLIENT_ID="sample_client" +MQTT_USERNAME="sample_client" +MQTT_PASSWORD="" +MQTT_CA_FILE="chain.pem" +MQTT_CERT_FILE="sample_client.pem" +MQTT_KEY_FILE="sample_client.key" +MQTT_KEY_FILE_PASSWORD="" diff --git a/mkdocs.yml b/mkdocs.yml index 374fc5f..7a6fa73 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -7,6 +7,7 @@ nav: - Home: index.md - Scenarios: - 1. Azure IoT Hub Messaging: scenarios/1_azure_iot_hub_messaging.md + - 2. Azure Event Grid Messaging: scenarios/2_azure_event_grid_messaging.md theme: name: material # https://squidfunk.github.io/mkdocs-material/setup/changing-the-colors/#automatic-light-dark-mode diff --git a/scripts/event_grid.py b/scripts/event_grid.py new file mode 100644 index 0000000..64b13bc --- /dev/null +++ b/scripts/event_grid.py @@ -0,0 +1,143 @@ +import logging +import ssl +import time + +import paho.mqtt.client as mqtt +import typer +from dotenv import load_dotenv + +app = typer.Typer() +logger = logging.getLogger(__name__) + + +def get_connection_settings( + host_name: str, + client_name: str, +) -> dict: + return { + "MQTT_HOST_NAME": host_name, + "MQTT_USERNAME": client_name, + "MQTT_CLIENT_ID": client_name, + "MQTT_CERT_FILE": f"configs/clients/{client_name}.pem", + "MQTT_KEY_FILE": f"configs/clients/{client_name}.key", + "MQTT_CA_FILE": "configs/mosquitto/chain.pem", + "MQTT_TCP_PORT": 8883, + "MQTT_USE_TLS": True, + "MQTT_CLEAN_SESSION": True, + "MQTT_KEEP_ALIVE_IN_SECONDS": 60, + "MQTT_KEY_FILE_PASSWORD": None, + } + + +def get_mqtt_client( + connection_settings: dict, +): + client = mqtt.Client( + client_id=connection_settings["MQTT_CLIENT_ID"], + clean_session=connection_settings["MQTT_CLEAN_SESSION"], + protocol=mqtt.MQTTv311, + transport="tcp", + ) + if "MQTT_USERNAME" in connection_settings: + client.username_pw_set( + username=connection_settings["MQTT_USERNAME"], + password=connection_settings["MQTT_PASSWORD"] if "MQTT_PASSWORD" in connection_settings else None, + ) + if connection_settings["MQTT_USE_TLS"]: + context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + context.minimum_version = ssl.TLSVersion.TLSv1_2 + context.maximum_version = ssl.TLSVersion.TLSv1_3 + + if connection_settings["MQTT_CERT_FILE"]: + context.load_cert_chain( + certfile=connection_settings["MQTT_CERT_FILE"], + keyfile=connection_settings["MQTT_KEY_FILE"], + password=connection_settings["MQTT_KEY_FILE_PASSWORD"], + ) + if connection_settings["MQTT_HOST_NAME"] == "localhost": + context.load_verify_locations( + cafile=connection_settings["MQTT_CA_FILE"], + ) + else: + context.load_default_certs() + + client.tls_set_context(context) + return client + + +def attach_functions(client: mqtt.Client) -> mqtt.Client: + client.on_connect = lambda client, userdata, flags, rc: logger.info( + f"on_connect: client={client}, userdata={userdata}, flags={flags}, rc={rc}" + ) + client.on_disconnect = lambda client, userdata, rc: logger.info( + f"on_disconnect: client={client}, userdata={userdata}, rc={rc}" + ) + client.on_message = lambda client, userdata, message: logger.info( + f"on_message: client={client}, userdata={userdata}, message={message.payload.decode()}" + ) + return client + + +@app.command() +def publish( + topic: str = "sample/topic1", + payload: str = "Hello, World!", + client_name: str = "client1", + host_name: str = "localhost", + verbose: bool = False, +): + if verbose: + logging.basicConfig(level=logging.DEBUG) + + connection_settings = get_connection_settings( + client_name=client_name, + host_name=host_name, + ) + mqtt_client = get_mqtt_client(connection_settings) + mqtt_client = attach_functions(mqtt_client) + + mqtt_client.connect( + host=connection_settings["MQTT_HOST_NAME"], + port=connection_settings["MQTT_TCP_PORT"], + ) + + mqtt_client.loop_start() + result = mqtt_client.publish( + topic=topic, + payload=payload, + ) + logger.info(result) + # fixme: wait for the message to be sent + time.sleep(1) + + mqtt_client.loop_stop() + + +@app.command() +def subscribe( + topic: str = "sample/topic1", + client_name: str = "client1", + host_name: str = "localhost", + verbose: bool = False, +): + if verbose: + logging.basicConfig(level=logging.DEBUG) + + connection_settings = get_connection_settings( + client_name=client_name, + host_name=host_name, + ) + mqtt_client = get_mqtt_client(connection_settings) + mqtt_client = attach_functions(mqtt_client) + + mqtt_client.connect( + host=connection_settings["MQTT_HOST_NAME"], + port=connection_settings["MQTT_TCP_PORT"], + ) + mqtt_client.subscribe(topic) + mqtt_client.loop_forever() + + +if __name__ == "__main__": + load_dotenv("event_grid.env") + app()