The target is to create a device that could map multiple functions. A toolbox for working with Cisco Meraki. To be able to implement these features easily, I have chosen a Raspberry Pi with the Raspbian operating system.
The following functions should be provided:
- Syslog Server
- MQTT Server
- Webhook Server
- SSH Service Tunnel
- Radius Server
- DHCP Server
- SAML SSO Server
The following explains how to install and set up the various functions.
https://www.raspberrypi.com/software/
Basic configuration for Raspbian
First, some basic settings can be made using the configuration tool provided by Raspbian:
sudo raspi-config
After that, we should do some updates:
sudo apt-get updates
sudo apt-get upgrades
Install Syslog
sudo apt-get install rsyslog
Open the syslog configuration
sudo nano /etc/rsyslog.conf
And remove the comment sign (#), the part should look like this:
# provides UDP syslog reception
module(load="imudp")
input(type="imudp" port="514")
# provides TCP syslog reception
module(load="imtcp")
input(type="imtcp" port="514")
Let's create an template:
sudo nano /etc/rsyslog.d/meraki.conf
Insert something like this:
$template merakilog, "/var/log/meraki.log"
$template merakimrlog, "/var/log/meraki_mr.log"
$template merakiflow, "/var/log/meraki_flows.log"
$template merakifw, "/var/log/meraki_firewall.log"
# ignore this messages:
if $msg contains 'ip_flow_start' or $msg contains 'ip_flow_end' then ~
& stop
if $msg contains 'firewall' then -?merakifw
& stop
if $fromhost-ip startswith ["172.24.0.101"] then -?merakimrlog
& stop
if $fromhost-ip startswith ["172.24.4.1", "172.24.2.254"] then -?merakilog
& stop
Restart the Service
sudo systemctl restart rsyslog
This command displays all Syslog messages in real time.
command tail -f /var/log/meraki.log
command tail -f /var/log/meraki_firewall.log
Now go to your Meraki Organization and configure Syslog:
Network-wide -> General -> Reporting
Server IP: 'IP from Raspberry'
Port: 514
Roles: Air Marshal events, Wireless event log, Switch event log, Security events, Appliance event log, Flows
sudo apt-get install mosquitto mosquitto-clients
First we allow access to our MQTT and set the port:
sudo nano /etc/mosquitto/conf.d/010-listener-with-users.conf
listener 1883
allow_anonymous true
Now restart the service and check if it is active:
sudo systemctl status mosquitto
sudo systemctl restart mosquitto
Now we can listen to different MQTT topics. As wildcard can be used #. To better understand which Topics are available, a brief overview of the current MQTT capabilities at Cisco Meraki.
Wildcards
- Plus sign (+): matches any name for a specific topic area ("/merakimv/+/0")
- Hash sign (#): multi level wildcard, can used at the end of the topic ("/merakimv/#")
Cameras
How to add an MQTT Broker:
Cameras -> Select one -> Settings -> Sense -> MQTT Broker
Broker Name: RPi
Host: 'IP from Raspberry'
Port: 1883
Which Topics exist:
- Object detections from whole camera frame:
/merakimv/Q2xx-xxxx-xxxx/raw_detections
- Current state of zone:
/merakimv/Q2xx-xxxx-xxxx/000000000000
- Audio detections from the camera’s microphone:
/merakimv/Q2xx-xxxx-xxxx/audio_detections
- Measurements from the camera’s microphone (dB):
/merakimv/Q2xx-xxxx-xxxx/audio_analytics
Environmental (IoT)
How to add an MQTT Broker:
Environmental -> MQTT Brokers ->
Name: RPi
Host: 'IP from Raspberry'
Port: 1883
Which Topics exist:
- Temperature, Humidity, Door, Water and Button data from an MT:
meraki/v1/mt/Network_Id/ble/{deviceMac}/temperature
meraki/v1/mt/Network_Id/ble/{deviceMac}/humidity
meraki/v1/mt/Network_Id/ble/{deviceMac}/door
meraki/v1/mt/Network_Id/ble/{deviceMac}/waterDetection
meraki/v1/mt/Network_Id/ble/{deviceMac}/buttonPressed - Cable and USB Power connected to an MT:
meraki/v1/mt/Network_Id/ble/{deviceMac}/cableConnected
meraki/v1/mt/Network_Id/ble/{deviceMac}/tamperDetection - Battery percentage and USB power status:
meraki/v1/mt/Network_Id/ble/{deviceMac}/usbPowered
meraki/v1/mt/Network_Id/ble/{deviceMac}/batteryPercentage - RSSI data for an MT:
meraki/v1/mt/Network_Id/ble/{deviceMac}/gateway/{gatewayMac}/rssi
Getting MQTT data
As we can see from the two topics, this one differs. We have the possibility to listen to only one topic at a time, but we also have the possibility to listen to several topics. With the wildcard we can listen to all subordinate topics.
mosquitto_sub -h 127.0.0.1 -v -t meraki/#
mosquitto_sub -h 127.0.0.1 -v -t /merakimv/#
mosquitto_sub -h 127.0.0.1 -v -t meraki/# -t /merakimv/#
mosquitto_sub -h 127.0.0.1 -v -t /merakimv/Q2xx-xxxx-xxxx/audio_analytics
The -t option allows us to specify a topic and the -v option shows us the entire topic path live.
What good is collecting MQTT data if we don't use it? Nothing!
So let's query the data with a Python script and use it.
First we need a Python Library for MQTT
pip install paho-mqtt
Now lets write a small script, which print all MQTT messages:
import paho.mqtt.client as mqtt
def on_message(client, userdata, message):
print("message received ", str(message.payload.decode("utf-8")))
print("message topic=", message.topic)
mqttBroker = "127.0.0.1" # ip of the server
client = mqtt.Client('my-mqtt-client') # create instance
client.connect(mqttBroker) # connect to broker
client.subscribe("meraki/v1/mt/#") # subscribe to topic
client.on_message = on_message # call function for messages
client.loop_forever() # start an endless loop
So we get all the messages and topics, to make sense of the data we have to look inside the data. (See file: mqtt_subscribe.py)
import paho.mqtt.client as mqtt
import json
def on_message(client, userdata, message):
data = json.loads(message.payload.decode("utf-8"))
if 'action' in data:
if data['action'] == 'shortPress':
print('Short Press - do something')
if data['action'] == 'longPress':
print('Long Press - do something')
if 'open' in data:
if data['open'] is True:
print('Door is open - do something')
mqttBroker = "127.0.0.1" # ip of the server
client = mqtt.Client('my-mqtt-client') # create instance
client.connect(mqttBroker) # connect to broker
client.subscribe("meraki/v1/mt/#") # subscribe to topic
client.on_message = on_message # call function for messages
client.loop_forever() # start an endless loop
A webhook is in "principle" the opposite direction to the API. So we need a server that reacts to incoming messages or executes actions. First we need a Python Library for a Webserver
pip install flask
Now lets write a small script, which print all incoming Webhook messages:
from flask import Flask, request, Response
app = Flask(__name__) # create instance
@app.route('/webhook', methods=['POST']) # define endpoint
def respond():
""" if a webhook is incoming, return status code 200 and print the json """
if request.method == 'POST': # if POST
print(request.json) # print webhook (json input)
return Response(status=200) # return status code 200
if __name__ == '__main__':
app.run(host='0.0.0.0', debug=True, port=19000)
Problem: Meraki webhooks are coming from Meraki Cloud. Accordingly, we need access from external. We now have two options. Either we open a port, which we forward to the Raspberry. Alternatively we can use ngrok to establish a connection from external.
How to use ngrok?
First download and install ngrok:
After this just run it for a specific port:
ngrok http 19000
How to use ngrok?
In order to be able to receive webhooks from Meraki, they still have to be configured:
Network-wide -> Alerts -> Webhooks
Name: Raspberry Pi
URL: https://00aa-00-00-000-00.ngrok.io/webhook
Shared secret: 'your secret'
Now you should get a webhook by clicking on "send test webhook".
If a Raspberry is to be used remotely, it is often useful to build a service tunnel to have access to the system at any time. To increase the comfort, the script should send the tunnel url to us via Webex. The first thing we need is pyngrok for our script.
pip install pyngrok
Now we can create a new python script
from pyngrok import ngrok
import requests
import json
ngrok_credentials = "xxxx"
webex_url = "https://api.ciscospark.com/v1/messages"
webex_access_token = "xxxx-xxx-xxx-xx-xxx"
headers = {'Authorization': 'Bearer ' + webex_access_token, 'Content-Type': 'application/json'}
webex_mails = ["Sebastian.Ehrhardt@blabla.de"]
# Initiate ngrok SSH Tunnel
ngrok.set_auth_token(ngrok_credentials)
ssh_tunnel = ngrok.connect(22, "tcp")
tunnel = str(ngrok.get_tunnels())[22:].split('" -> "')[0]
# Send all Information to Webex
message = f'*Raspberry Pi - DevNet Demo*\n System is now online\n SSH Service Tunnel is successfully started on {tunnel}'
for mail in webex_mails:
payload = json.dumps({"toPersonEmail": mail, "markdown": message, "text": message})
response = requests.request("POST", webex_url, headers=headers, data=payload)
while True: # to keep the ngrok tunnel up
do_nothing = 1
I decided to use FreeRADIUS in the backend and daloRADIUS in the frontend.