I am trying to setup AWS IoT in Pi on port 443 using Paho MQTT .
As AWS document (https://docs.aws.amazon.com/iot/latest/developerguide/protocols.html) mentioned that
Clients wishing to connect using MQTT with X.509 Client Certificate authentication on port 443 must implement the Application Layer Protocol Negotiation (ALPN) TLS extension and pass x-amzn-mqtt-ca as the ProtocolName in the ProtocolNameList.
I actually don't know how to achieve it properly in Paho MQTT (https://github.com/eclipse/paho.mqtt.python)
What I tried to do (mqtt_apln.py)
import sys
import ssl
import time
import datetime
import logging, traceback
import paho.mqtt.client as mqtt
MQTT_TOPIC = "topictest"
MQTT_MSG = "hello MQTT"
IoT_protocol_name = "x-amzn-mqtt-ca"
aws_iot_endpoint = "xxxxxxx.iot.eu-west-1.amazonaws.com"
url = "https://{}".format(aws_iot_endpoint)
ca = ".xxxxx/rootCA.pem"
cert = ".xxxxx/xxxxx-certificate.pem.crt"
private = ".xxxxx/xxxxxx-private.pem.key"
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)
handler = logging.StreamHandler(sys.stdout)
log_format = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(log_format)
logger.addHandler(handler)
# Define on connect event function
# We shall subscribe to our Topic in this function
def on_connect(mosq, obj, rc):
mqttc.subscribe(MQTT_TOPIC, 0)
# Define on_message event function.
# This function will be invoked every time,
# a new message arrives for the subscribed topic
def on_message(mosq, obj, msg):
print "Topic: " + str(msg.topic)
print "QoS: " + str(msg.qos)
print "Payload: " + str(msg.payload)
def on_subscribe(mosq, obj, mid, granted_qos):
print("Subscribed to Topic: " +
MQTT_MSG + " with QoS: " + str(granted_qos))
def ssl_alpn():
try:
#debug print opnessl version
logger.info("open ssl version:{}".format(ssl.OPENSSL_VERSION))
ssl_context = ssl.create_default_context()
ssl_context.set_alpn_protocols([IoT_protocol_name])
ssl_context.load_verify_locations(cafile=ca)
ssl_context.load_cert_chain(certfile=cert, keyfile=private)
return ssl_context
except Exception as e:
print("exception ssl_alpn()")
raise e
mqttc = mqtt.Client()
# Assign event callbacks
mqttc.on_message = on_message
mqttc.on_connect = on_connect
mqttc.on_subscribe = on_subscribe
ssl_context= ssl_alpn()
mqttc.tls_set_context(context=ssl_context)
logger.info("start connect")
mqttc.connect(aws_iot_endpoint, port=443)
logger.info("connect success")
mqttc.loop_start()
In Pi, I installed python 2.7.14 and paho-mqtt
But When I run python mqtt_apln.py, it shows error: ImportError: No module named paho.mqtt.client
Any suggestion is appreciated
I think there are two things going on here. First, a pip install paho-mqtt should make the package active for the current referenced python. For instance, under a 3.6.2 virtualenv should return:
$ pip list
DEPRECATION: The default format will switch to columns in the future. You can use --format=(legacy|columns) (or define a format=(legacy|columns) in your
pip.conf under the [list] section) to disable this warning.
paho-mqtt (1.3.1)
pip (9.0.1)
setuptools (28.8.0)
How did you install the paho-mqtt package, via an apt package or directly with pip? Personally I virtualenv everything or include the package within the application directory via pip install package_name -t . to reference the current working directory.
From there it's working with the ALPN configuration. I reduced your code to just publish to the test topic on my end-point and used the AWS IoT console-->Test to subscribe to the test topic. For both python 2.7.12 and 3.6.2 I successfully received messages.
The main changes were to remove the callbacks, place a mqttc.publish followed by a time.sleep(3) to give the thread time to publish, then closed the connection.
Here is the code paired down for the publish only:
import sys
import ssl
import time
import datetime
import logging, traceback
import paho.mqtt.client as mqtt
MQTT_TOPIC = "topictest"
MQTT_MSG = "hello MQTT"
IoT_protocol_name = "x-amzn-mqtt-ca"
aws_iot_endpoint = "xxxxxx.iot.us-east-1.amazonaws.com"
ca = "ca.pem"
cert = "xxxx-certificate.pem.crt"
private = "xxxx-private.pem.key"
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)
handler = logging.StreamHandler(sys.stdout)
log_format = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(log_format)
logger.addHandler(handler)
def ssl_alpn():
try:
#debug print opnessl version
logger.info("open ssl version:{}".format(ssl.OPENSSL_VERSION))
ssl_context = ssl.create_default_context()
ssl_context.set_alpn_protocols([IoT_protocol_name])
ssl_context.load_verify_locations(cafile=ca)
ssl_context.load_cert_chain(certfile=cert, keyfile=private)
return ssl_context
except Exception as e:
print("exception ssl_alpn()")
raise e
mqttc = mqtt.Client()
ssl_context= ssl_alpn()
mqttc.tls_set_context(context=ssl_context)
logger.info("start connect")
mqttc.connect(aws_iot_endpoint, port=443)
logger.info("connect success")
mqttc.loop_start()
# After loop start publish and wait for message to be sent.
# Hard coded delay but would normally tie into event loop
# or on_publish() CB
# JSON payload because, pretty
mqttc.publish('test', '{"foo": "bar"}')
time.sleep(3)
mqttc.loop_stop()
Please let me know if that works for you? It's awesome that AWS now supports MQTT connections on port 443 without having to utilize websockets (and the need for SigV4 credentials).
I ran into the same issue regarding the use of paho-mqtt with aws IoT core.
https://aws.amazon.com/de/blogs/iot/how-to-implement-mqtt-with-tls-client-authentication-on-port-443-from-client-devices-python/
The tutorial uses no clientID. Depending on your security rules you have to deliver a client ID to be able to connect properly. Here an SDK-example ruleset where only the clients "sdk-java", "basicPubSub" and "sdk-nodejs-*" are allowed to connect.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"iot:Publish",
"iot:Receive"
],
"Resource": [
"arn:aws:iot:eu-central-1:036954049003:topic/sdk/test/java",
"arn:aws:iot:eu-central-1:036954049003:topic/sdk/test/Python",
"arn:aws:iot:eu-central-1:036954049003:topic/topic_1",
"arn:aws:iot:eu-central-1:036954049003:topic/topic_2"
]
},
{
"Effect": "Allow",
"Action": [
"iot:Subscribe"
],
"Resource": [
"arn:aws:iot:eu-central-1:036954049003:topicfilter/sdk/test/java",
"arn:aws:iot:eu-central-1:036954049003:topicfilter/sdk/test/Python",
"arn:aws:iot:eu-central-1:036954049003:topicfilter/topic_1",
"arn:aws:iot:eu-central-1:036954049003:topicfilter/topic_2"
]
},
{
"Effect": "Allow",
"Action": [
"iot:Connect"
],
"Resource": [
"arn:aws:iot:eu-central-1:036954049003:client/sdk-java",
"arn:aws:iot:eu-central-1:036954049003:client/basicPubSub",
"arn:aws:iot:eu-central-1:036954049003:client/sdk-nodejs-*"
]
}
]
}
To allow connection in case you have permits based on clientid change this line:
mqttc = mqtt.Client(client_id=MYCLIENTID)
MYCLIENTID is one of the three "sdk-java", "basicPubSub" or "sdk-nodejs-*" for this example.
Related
I am running a local Mosquitto (MQTT) Broker that connects to a remote Mosquitto Broker using the build in MQTT Bridge functionality of Mosquitto. My mosquitto.conf looks like this:
# =================================================================
# Listeners
# =================================================================
listener 1883
# =================================================================
# Security
# =================================================================
allow_anonymous true
# =================================================================
# Bridges
# =================================================================
connection myConnectionName
address <<Remote Broker IP>>:1883
remote_username <<Remote Broker Username>>
remote_password <<Remote Broker Password>>
topic mytopic/# out 1 "" B2/
bridge_protocol_version mqttv50
cleansession false
bridge_attempt_unsubscribe true
upgrade_outgoing_qos true
max_queued_messages 5000
For testing I run a MqttPublisher using a C# console application which uses the MQTTnet library (version 3) and a MqttSubsbriber (also C# console application with MqttNet).
Now I want the Publisher to publish MQTT messages with User Properties (introduced by MQTT 5).
I build the message like this:
using MQTTnet;
using MQTTnet.Client;
using MQTTnet.Client.Options;
class Program
{
static void Main()
{
// Create a new MQTT client instance
var factory = new MqttFactory();
var mqttClient = factory.CreateMqttClient();
// Setup the options for the MQTT client
var options = new MqttClientOptionsBuilder()
.WithClientId("MqttPublisher")
.WithTcpServer("localhost", 1883)
.WithProtocolVersion(MQTTnet.Formatter.MqttProtocolVersion.V500)
.WithCleanSession()
.Build();
mqttClient.ConnectAsync(options).Wait();
var i = 0;
while (true)
{
Console.WriteLine("Client connected: " + mqttClient.IsConnected);
var message = new MqttApplicationMessageBuilder()
.WithTopic("mytopic/test")
.WithUserProperty("UPTest","Hi UP")
.WithPayload("Hello World: " + i)
.Build();
mqttClient.PublishAsync(message).Wait();
Console.WriteLine("Published message with payload: " + System.Text.Encoding.UTF8.GetString(message.Payload));
i++;
System.Threading.Thread.Sleep(1000);
}
mqttClient.DisconnectAsync().Wait();
}
}
With the subscriber (also with WithProtocolVersion(MQTTnet.Formatter.MqttProtocolVersion.V500) if I subscribe to the topic I get all the messages and I can read the MQTTnet.MqttApplicationMessage like shown in the following screenshot:
The messages are also published to the remote MQTT Broker due to the MQTT Bride configured. However, if I subscribe to the remote Broker with my MqttSubscriber, I am not getting the User Properties anymore:
Is there any way to configure the Mosquitto Bridge that also the user properties are send? I cant find a way and any help and comments are appreciated.
Thanks
Joshua
Using mosqutto 2.0.15 I have verified that MQTTv5 message properties are passed over the bridge.
Broker one.conf
listener 1883
allow_anonymous true
Broker two.conf
listener 1884
allow_anonymous true
connection one
address 127.0.0.1:1883
topic foo/# both 0
bridge_protocol_version mqttv50
Subscriber connected to Broker two
$ mosquitto_sub -t 'foo/#' -V mqttv5 -p 1884 -F "%t %P %p"
foo/bar foo:bar ben
Publisher connected to Broker one
$ mosquitto_pub -D PUBLISH user-property foo bar -t 'foo/bar' -m ben -p 1883
As you can see the the %P in the output format for the subscriber is outputting the user property foo with a value of bar when subscribed over the bridge.
I'm trying to get my webapp to send messages and I can't figure out why it isn't working. There are no errors that I can see, it's just that the actions in my event.py function aren't happening. I am running a gunicorn server with eventlet workers serving a flask app.
Here's the command that starts the gunicorn server through docker:
CMD [ "gunicorn", "--reload", "-b", "0.0.0.0:5000", "--worker-class", "eventlet", "-w", "1", "app:app"]
here's the relevant code on notes.html:
// Imports socketio
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.1/socket.io.js" integrity="sha512-q/dWJ3kcmjBLU4Qc47E4A9kTB4m3wuTY7vkFJDTZKjTs8jhyGQnaUrxa0Ytd0ssMZhbNua9hE+E7Qv1j+DyZwA==" crossorigin="anonymous"></script>
<script type="text/javascript" charset="utf-8">
// sets domain to talk to. (empty sets it to localhost)
const socket = io()
// send message to server on trigger from form.
socket.emit('send_new_session', new_session_form_id.value, new_session_form_number.value, new_session_form_title.value, new_session_form_synopsis.value)
console.log('send_new_session')
// console logs the message here, do I know it's getting this far. The problem seems to be that the server isn't getting the message for some reason.
events.py:
from . import db, socketio
from .classes import *
from flask_socketio import emit
#socketio.on('send_new_session')
def send_new_session(id, number, title, synopsis=None):
print("arrived!!!!!!!!!!!")
# more code that adds the new session the the database
..
I have the logging correctly set up to stdout but I never see the "arrived" message, so I know it's never hitting the server.
here is the server logs for when I send the message:
rest-server | Bpt-ydbpGYLF-HGKAAAC: Sending packet OPEN data {'sid': 'Bpt-ydbpGYLF-HGKAAAC', 'upgrades': ['websocket'], 'pingTimeout': 20000, 'pingInterval': 25000}
rest-server | Bpt-ydbpGYLF-HGKAAAC: Received packet MESSAGE data 0
rest-server | Bpt-ydbpGYLF-HGKAAAC: Sending packet MESSAGE data 0{"sid":"MRAxFiGYyLB3C6MBAAAD"}
rest-server | Bpt-ydbpGYLF-HGKAAAC: Received request to upgrade to websocket
rest-server | Bpt-ydbpGYLF-HGKAAAC: Upgrade to websocket successful
rest-server | Bpt-ydbpGYLF-HGKAAAC: Received packet MESSAGE data 2["send_new_session","1","2","foo","bar"]
rest-server | received event "send_new_session" from MRAxFiGYyLB3C6MBAAAD [/]
rest-server | Bpt-ydbpGYLF-HGKAAAC: Sending packet PING data None
rest-server | Bpt-ydbpGYLF-HGKAAAC: Received packet PONG data
you can see in the log that the message is in fact being sent and received, but for some reason the actions in the event aren't happening. I've been trying everything I can think of for a couple days not. any help would be greatly appreciated!!
Everything below here is probably not relevant, but if it it helps, this is how I set up the app:
file set up:
/app
--app.py
--requirements.txt
--Dockerfile
--docker-compose.yml
--.flaskenv
--/project
----/static
----/templates
----__init__.py
----settings.py
----events.py
----BONapp.py
----auth.py
etc...
settings.py
import os
from flask import Flask
app = Flask(__name__)
db_password = os.environ.get('DB_PASS')
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://root:' + db_password + '#bonmysqldb:3306/BON'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.config['SECRET_KEY'] = db_password
init.py
from flask_login import LoginManager
from flask_migrate import Migrate
from flask_sqlalchemy import SQLAlchemy
from flask_socketio import SocketIO
from .settings import app
db = SQLAlchemy(app)
socketio = SocketIO(app, logger=True, engineio_logger=True)
def create_app():
migrate = Migrate(app, db)
from .classes import Users
db.init_app(app)
socketio.init_app(app)
login_manager = LoginManager()
login_manager.login_view = 'auth.login'
login_manager.init_app(app)
# provide login_manager with a unicode user ID
#login_manager.user_loader
def load_user(user_id):
return Users.query.get(int(user_id))
# blueprint for auth routes of app
from .auth import auth as auth_blueprint
app.register_blueprint(auth_blueprint)
# blueprint for non-auth parts of app
from .BONapp import main as main_blueprint
app.register_blueprint(main_blueprint)
return app
app.py
from project.__init__ import create_app
app = create_app()
I figured it out while reading over my question again...
it was in events.py I changed:
from . import db, socketio
to:
from .__init__ import db, socketio
I'm not exactly sure why that mattered but it fixed it.
facepalm
I have registered my ESP32 as a thing on AWS IoT and downloaded its respective certificate and public & private keys. Also verified that those connect properly via the following command in my terminal:
openssl s_client -connect host.iot.region.amazonaws.com:8443 -CAfile AmazonRootCA1.pem -cert certificate.pem.crt -key private.pem.key
This is my (main.py) simple code to connect to AWS IoT using MicroPython
import machine
from network import WLAN
import network
from umqtt.simple import MQTTClient
# AWS endpoint parameters.
HOST = b'HOST' # ex: b'abcdefg1234567'
REGION = b'REGION' # ex: b'us-east-1'
CLIENT_ID = "CLIENT_ID" # Should be unique for each device connected.
AWS_ENDPOINT = b'%s.iot.%s.amazonaws.com' % (HOST, REGION)
keyfile = '/certs/private.pem.key'
with open(keyfile, 'r') as f:
key = f.read()
certfile = "/certs/certificate.pem.crt"
with open(certfile, 'r') as f:
cert = f.read()
# SSL certificates.
SSL_PARAMS = {'key': key,'cert': cert, 'server_side': False}
# Setup WiFi connection.
wlan = network.WLAN( network.STA_IF )
wlan.active( True )
wlan.connect( "SSID", "PASSWORD" )
while not wlan.isconnected():
machine.idle()
# Connect to MQTT broker.
mqtt = MQTTClient( CLIENT_ID, AWS_ENDPOINT, port = 8883, keepalive = 10000, ssl = True, ssl_params = SSL_PARAMS )
mqtt.connect()
# Publish a test MQTT message.
mqtt.publish( topic = 'test', msg = 'hello world', qos = 0 )
But I get this error when I try to connect:
(-17168, 'MBEDTLS_ERR_RSA_PRIVATE_FAILED+MBEDTLS_ERR_MPI_ALLOC_FAILED')
After much effort I got this to work. I had to use an idf3 MicroPython binary,
esp32-idf3-20191220-v1.12.bin
idf4 binaries and idf3 later than v1.12 don't work. There is a problem with not enough heap and memory allocation problems.
----------- EDIT -----------
News update! The new v1.15 release of MicroPython based on idf4 works with AWS MQTT for IoT.
Setup:
I have 3 docker containers
1) For Kafka
2) For Zookeeper
3) For JupyterLab
I setup networking between these containers and I see that kafka producer is able to run and produce the data.
KafkaProducer.ipynb
KAFKA_BROKER = ['172.20.0.2:9093']
from kafka import KafkaProducer
from kafka.errors import KafkaError
producer = KafkaProducer(bootstrap_servers=KAFKA_BROKER)
for _ in range(100):
print("sending")
producer.send('my-topic', key=b'foo', value=b'bar')
print("success")
Here the send() sends message 100 times.
KafkaConsumer.ipynb
KAFKA_BROKER = ['172.20.0.2:9093']
from kafka import KafkaConsumer
consumer = KafkaConsumer('my-topic',group_id='my-group',bootstrap_servers=KAFKA_BROKER)
print("Comm success")
for message in consumer:
# message value and key are raw bytes -- decode if necessary!
# e.g., for unicode: `message.value.decode('utf-8')`
print ("%s:%d:%d: key=%s value=%s" % (message.topic, message.partition,
message.offset, message.key,
message.value))
In the above consumer code the line print("Comm success") never gets gets executed. Based on producer code execution, the network is open and jupyter is able to talk to kafka broker. But, client is not able to connect to the same broker for data consumption. How can I start debugging this?
By default auto.offset.reset value is latest, so set it to earliest with new group.id
consumer = KafkaConsumer('my-topic',group_id='new-group',auto_offset_reset = 'earliest',bootstrap_servers=KAFKA_BROKER)
My target is using AWS Beanstalk, create application environment type 'Worker' which will handle heavy loading tasks, this worker based on our Rails application.
I create AWS Beanstalk Worker Environment:
Environment tier: Ruby, 1.9.3 on 64bit Amazon Linux
Environment type: single instance
(i did try 64bit Amazon Linux 2014.03 v1.0.3 running Ruby 2.0 (Puma) with same failed result)
After solving all issues with GEMS and database connection, i stuck on starting "aws-sqs" Queue client. It should listen Queue and do HTTP requests to Worker application.
I've provide AWS_ACCESS_KEY_ID and AWS_SECRET_KEY to ENV variables for this Worker instance:
$ export | grep AWS
declare -x AWS_ACCESS_KEY_ID="AK...........Q"
declare -x AWS_AUTO_SCALING_HOME="/opt/aws/apitools/as"
declare -x AWS_CLOUDWATCH_HOME="/opt/aws/apitools/mon"
declare -x AWS_ELB_HOME="/opt/aws/apitools/elb"
declare -x AWS_IAM_HOME="/opt/aws/apitools/iam"
declare -x AWS_PATH="/opt/aws"
declare -x AWS_RDS_HOME="/opt/aws/apitools/rds"
declare -x AWS_SECRET_KEY="Hp.....fI"
declare -x EB_CONFIG_SYSTEM_AWSEBAGENTID=""
declare -x EB_CONFIG_SYSTEM_AWSEBREFERRERID=""
Here is log output:
2014-05-19T13:58:59Z init: initializing aws-sqsd 1.0 (2013-12-23)
2014-05-19T13:58:59Z start: polling https://sqs.us-east-1.amazonaws.com/201266939336/awseb-e-dq8cqaud2z-stack-AWSEBWorkerQueue-18836XBBHNDUD
2014-05-19T13:58:59Z fatal: AWS::Errors::MissingCredentialsError:
Missing Credentials.
Unable to find AWS credentials. You can configure your AWS credentials
a few different ways:
* Call AWS.config with :access_key_id and :secret_access_key
<<<
* On EC2 you can run instances with an IAM instance profile and credentials
will be auto loaded from the instance metadata service on those
instances.
* Call AWS.config with :credential_provider. A credential provider should
either include AWS::Core::CredentialProviders::Provider or respond to
the same public methods.
= Ruby on Rails
In a Ruby on Rails application you may also specify your credentials in
the following ways:
* Via a config initializer script using any of the methods mentioned above
(e.g. RAILS_ROOT/config/initializers/aws-sdk.rb).
* Via a yaml configuration file located at RAILS_ROOT/config/aws.yml.
This file should be formated like the default RAILS_ROOT/config/database.yml
file.
Also i have config/initializers/aws-sdk.rb in my Rails application with this content:
AWS.config(
access_key_id: ENV["AWS_ACCESS_KEY_ID"],
secret_access_key: ENV["AWS_SECRET_ACCESS_KEY"])
Daemon aws-sqs don't started at all.
May i have chance to configure aws-sqs in some other way?
Perhaps the instance profile you are using for your Elastic Beanstalk does not have the permissions needed for worker environments.
http://docs.aws.amazon.com/elasticbeanstalk/latest/dg/AWSHowTo.iam.roles.aeb.html#AWSHowTo.iam.policies.actions.worker
Can you make sure your IAM Instance profile has all permissions listed in the link above?
(Copied below for reference)
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "QueueAccess",
"Action": [
"sqs:ChangeMessageVisibility",
"sqs:DeleteMessage",
"sqs:ReceiveMessage"
],
"Effect": "Allow",
"Resource": "*"
},
{
"Sid": "MetricsAccess",
"Action": [
"cloudwatch:PutMetricData"
],
"Effect": "Allow",
"Resource": "*"
}
]
}