-
Notifications
You must be signed in to change notification settings - Fork 5
Tutorial
We will build a small, autonomous IoT system with two HTTP REST services, an MQTT broker and a dummy WebSocket application and protect everything using the Border Gateway.
This tutorial will set up an OpenID Connect provider and the Border Gateway with TLS encryption using self-signed certificates (created for dummy domain names openid-ssl
and bgw-ssl
) locally on your machine using Docker and Docker Compose. To be able to access these components using the dummy domain names, add the following lines to your local hosts
file:
127.0.0.1 openid-ssl
127.0.0.1 bgw-ssl
- Windows:
C:\Windows\System32\drivers\etc\hosts
- Linux:
/etc/hosts
git clone https://github.com/linksmart/border-gateway.git
Set an environment variable BGW_HOME
to the path of the cloned repository.
cd $BGW_HOME/test/openid
docker-compose -f docker-compose-tutorial.yml up -d
This will set up an instance of Keycloak with a Postgres backend and an nginx webserver. Wait until everything is up and running and make sure you can login at https://openid-ssl/auth/admin/ using credentials keycloak
/ keycloak
. The certificate is self-signed, you can find the CA certificate at $BGW_HOME/certs/CA.pem
.
cd $BGW_HOME/test/backend
docker-compose -f docker-compose-tutorial.yml up -d
This will start up the LinkSmart Service Catalog, a dummy REST service, a Mosquitto MQTT broker and a WebSocket dummy application.
cd $BGW_HOME/test/tutorial
docker-compose -f docker-compose-tutorial.yml up -d
The bgw-external-interface
service will listen on port 8443 for https connections, on port 8883 for mqtts connections and on port 9002 for wss connections. The certificate is self-signed, you can find the CA certificate at $BGW_HOME/certs/CA.pem
.
Note that in Border Gateway´s configuration file $BGW_HOME/test/tutorial/config.toml
we define rule HTTPS/GET/#
to be used for anonymous requests (more on authorization rules [Authentication and Authorization](Authentication and Authorization)):
[...]
# Configuration of default OpenID Connect provider
[auth-service.openid_connect_providers.default]
[...]
anonymous_bgw_rules = "HTTPS/GET/#"
[...]
So we allow read-only anonymous HTTPS requests. Also we configured the path /sc
to be forwarded to Service Catalog running at sc:8082
in the user-defined Docker network:
[...]
# Configuration of location / path sc
[http-proxy.domains."bgw-ssl".sc]
# Local address to forward requests to
local_address = "http://sc:8082"
[...]
So you should be able to access page https://bgw-ssl:8443/sc/main_broker in the browser and see output like this:
{
"id": "main_broker",
"description": "MQTT Broker",
"name": "_mqtt._tcp",
"apis": {
"MQTT": "ssl://bgw-ssl:8883"
},
"docs": null,
"meta": {
"registrator": "2e2714b4-eb7f-4694-8422-3ceaee763199"
},
"ttl": 600,
"created": "2019-08-19T05:52:12.0872489Z",
"updated": "2019-08-19T06:52:12.0978012Z",
"expires": "2019-08-19T07:02:12.0978012Z"
}
Since GET is the the only http method allowed for anonymous access, other methods (e.g. PUT, POST, etc.) will not be executed. If you try, the Border Gateway will redirect you to the Keycloak login page (which is not useful for curl of course... we will see below how this works out when using the browser). Here is an example if we tried to add the dummy REST service to Service Catalog (we will get a http error message 302):
curl -v --cacert $BGW_HOME/certs/CA.pem -X PUT https://bgw-ssl:8443/sc/helloworld -d '{"name": "helloworld","description": "helloworld-express-app","apis": {"HTTP": "http://helloworld-express-app:8080"}, "ttl": 86400}' -H "accept: application/json"
So we need to provide an authorization header if we want to PUT. There is a user tutorial
(with password tutorial
) defined in Keycloak that has (among others) authorization rule HTTPS/PUT/#
. So if you provide the correct basic authorization header (providing Base64 encoded username and password), the following REST API call and be performed:
curl -v --cacert $BGW_HOME/certs/CA.pem -X PUT https://bgw-ssl:8443/sc/helloworld -d '{"name": "helloworld","description": "helloworld-express-app","apis": {"HTTP": "http://helloworld-express-app:8080"}, "ttl": 86400}' -H "accept: application/json" -H "Authorization: Basic dHV0b3JpYWw6dHV0b3JpYWw="
Note that a new service has been registered with Service Catalog visible at https://bgw-ssl:8443/sc/helloworld.
You can also retrieve an access token from the OpenID Connect provider and use it to authorize yourself:
curl --cacert $BGW_HOME/certs/CA.pem -X POST https://openid-ssl/auth/realms/realm1/protocol/openid-connect/token -d "username=tutorial&password=tutorial&grant_type=password&client_id=bgw_client&client_secret=d6e735a2-7554-499a-b9af-a0838ec23a1f" | jq
Provide the access token as bearer token to update the service:
curl -v --cacert $BGW_HOME/certs/CA.pem -X PUT https://bgw-ssl:8443/sc/helloworld -d '{"name": "helloworld","description": "helloworld-express-app","apis": {"HTTP": "http://helloworld-express-app:8080"}, "ttl": 86400}' -H "accept: application/json" -H "Authorization: Bearer <access_token>"
Note that when we registered the dummy REST service with Service catalog, we gave the internal API address http://helloworld-express-app:8080 in the body of the PUT request. When we check https://bgw-ssl:8443/sc/helloworld though, we see API address https://bgw-ssl:8443/helloworld. This is done by Border Gateway's [Address Translation](Address Translation). HTTP responses are inspected for internal addresses of the IoT network and these are being translated into addresses that are accessible externally via Border Gateway. The same goes for the broker address shown at https://bgw-ssl:8443/sc/main_broker. It was originally registered in the Service Catalog config file as tcp://mosquitto:1883 and is being translated by Border Gateway for external access.
MQTT via Border Gateway is not configured for anonymous access as you can see when trying:
mosquitto_sub -h bgw-ssl -p 8883 -d -t 'tutorial' --cafile $BGW_HOME/certs/CA.pem
Client mosqsub|814-imagon sending CONNECT
Client mosqsub|814-imagon received CONNACK
Connection Refused: not authorised.
The tutorial
user has authorization rule MQTT/+/mosquitto/1883/tutorial
so this will work:
mosquitto_sub -h bgw-ssl -p 8883 -d -t 'tutorial' --cafile $BGW_HOME/certs/CA.pem -u tutorial -P tutorial
Client mosqsub|815-imagon sending CONNECT
Client mosqsub|815-imagon received CONNACK
Client mosqsub|815-imagon sending SUBSCRIBE (Mid: 1, Topic: #, QoS: 0)
Client mosqsub|815-imagon received SUBACK
Subscribed (mid: 1): 0
You can also retrieve an access token as described above and provide it in the username field (do not provide a password field):
mosquitto_sub -h bgw-ssl -p 8883 -d -t 'tutorial' --cafile $BGW_HOME/certs/CA.pem -u "<access_token>"
Client mosqsub|1050-imagon sending CONNECT
Client mosqsub|1050-imagon received CONNACK
Client mosqsub|1050-imagon sending SUBSCRIBE (Mid: 1, Topic: #, QoS: 0)
Client mosqsub|1050-imagon received SUBACK
Subscribed (mid: 1): 0
We can use the tool [wscat](https://github.com/websockets/wscat)
to test a connection via WebSocket. Anonymous access is not allowed:
wscat --ca $BGW_HOME/certs/CA.pem --connect wss://bgw-ssl:9002/
connected (press CTRL+C to quit)
disconnected (code: 1008)
User tutorial
has authorization rule WS/CONNECT/+/+/#
, so connecting is allowed:
wscat --ca $BGW_HOME/certs/CA.pem --connect wss://bgw-ssl:9002/?basic_auth=dHV0b3JpYWw6dHV0b3JpYWw
connected (press CTRL+C to quit)
< Received ping
You can also retrieve an access token as described above and provide it like this:
wscat --ca $BGW_HOME/certs/CA.pem --connect wss://bgw-ssl:9002/?access_token=<access_token>
connected (press CTRL+C to quit)
< Received ping
When you try to send or receive data, you will be disconnected as soon as the token lifespan is over.
Let's say you want to limit access for user tutorial
to https://bgw-ssl:8443/sc/main_broker only and disallow all other paths. First we need to disallow anonymous access completely. To do this, comment out the anonymous_bgw_rules
definition for the default OpenID Connect provider in config.toml
:
[auth-service.openid_connect_providers.default]
[...]
#anonymous_bgw_rules = "HTTPS/GET/#"
We need to restart Border Gateway for the change to take effect.
cd $BGW_HOME/test/tutorial
docker-compose -f docker-compose-tutorial.yml restart bgw-ssl
When trying to open https://bgw-ssl:8443/sc/main_broker in the browser, anonymous access is not allowed and you will be forwarded to the Keycloak login page. Log in there with tutorial / tutorial
and you will be redirected to https://bgw-ssl:8443/sc/main_broker.
Now log in to https://openid-ssl/auth/admin/ as keycloak / keycloak
. Go to Realm1, Users, Edit user tutorial
, Attributes and replace the value of key bgw_rules
with value
HTTPS/+/bgw-ssl/8443/sc/main_broker
Make sure to hit the Save button. This will not take effect immediately, since the access tokens are cached in redis (default lifespan in Keycloak is five minutes). To speed things up, just restart redis:
cd $BGW_HOME/test/tutorial
docker-compose -f docker-compose-tutorial.yml restart redis
You can now try out that access to https://bgw-ssl:8443/sc is forbidden or user tutorial
, yet all http methods are allowed for path https://bgw-ssl:8443/sc/main_broker.
We want to limit subscription rights of user tutorial
to a certain topic. Let´s log into Keycloak Admin Console again and change the value of key bgw_rules
to
HTTPS/+/bgw-ssl/8443/sc/main_broker MQTT/CON/mosquitto/1883/# MQTT/SUB/mosquitto/1883/tutorial
Since bgw-mqtt-proxy service is not aware of bgw-external-interface service, we need to use the broker´s internal hostname and port in the rule definition. Hit the Save button and restart redis:
cd $BGW_HOME/test/tutorial
docker-compose -f docker-compose-tutorial.yml restart redis
Now tutorial
is allowed to connect and subscribe to topic tutorial
:
mosquitto_sub -h bgw-ssl -p 8883 -d -t 'tutorial' --cafile $BGW_HOME/certs/CA.pem -u tutorial -P tutorial
Client mosqsub|1391-imagon sending CONNECT
Client mosqsub|1391-imagon received CONNACK
Client mosqsub|1391-imagon sending SUBSCRIBE (Mid: 1, Topic: tutorial, QoS: 0)
Client mosqsub|1391-imagon received SUBACK
Subscribed (mid: 1): 0
But the user is not allowed to subscribe to any other topic:
mosquitto_sub -h bgw-ssl -p 8883 -d -t 'other' --cafile $BGW_HOME/certs/CA.pem -u tutorial -P tutorial
Client mosqsub|1396-imagon sending CONNECT
Client mosqsub|1396-imagon received CONNACK
Client mosqsub|1396-imagon sending SUBSCRIBE (Mid: 1, Topic: other, QoS: 0)
Client mosqsub|1396-imagon received SUBACK
Subscribed (mid: 1): 128
Note that the return code in the SUBACK
is 128 which represents failure. So you will not receive messages published on this topic.
Now let's have a look at publications. First subscribe to topic tutorial
again:
mosquitto_sub -h bgw-ssl -p 8883 -d -t 'tutorial' --cafile $BGW_HOME/certs/CA.pem -u tutorial -P tutorial
Client mosqsub|1391-imagon sending CONNECT
Client mosqsub|1391-imagon received CONNACK
Client mosqsub|1391-imagon sending SUBSCRIBE (Mid: 1, Topic: tutorial, QoS: 0)
Client mosqsub|1391-imagon received SUBACK
Subscribed (mid: 1): 0
Now open another console windows and publish something:
mosquitto_pub -h bgw-ssl -p 8883 -d -t 'tutorial' -m 'hello' -q 2 --cafile $BGW_HOME/certs/CA.pem -u tutorial -P tutorial
Client mosqpub|1461-imagon sending CONNECT
Client mosqpub|1461-imagon received CONNACK
Client mosqpub|1461-imagon sending PUBLISH (d0, q2, r0, m1, 'tutorial', ... (5 bytes))
Client mosqpub|1461-imagon received PUBREC (Mid: 1)
Client mosqpub|1461-imagon sending PUBREL (Mid: 1)
Client mosqpub|1461-imagon received PUBCOMP (Mid: 1)
Client mosqpub|1461-imagon sending DISCONNECT
There is no standard way of telling a publisher that his publication is not allowed so from the publisher´s perspective, everything looks fine. Note though that the subscriber will not receive the message as it is not actually published but dropped by the Border Gateway. Now allow any MQTT method for the topic and user tutorial:
HTTPS/+/bgw-ssl/8443/sc/main_broker MQTT/CON/mosquitto/1883/# MQTT/+/mosquitto/1883/tutorial
Restart redis again:
cd $BGW_HOME/test/tutorial
docker-compose -f docker-compose-tutorial.yml restart redis
Now publish again and you will see that the subscriber receives the message as a publication to topic tutorial
is allowed for user tutorial
.
You can configure the Border Gateway to demand a client certificate for the TLS connection to be established. Change the following line in $BGW_HOME/test/tutorial/config.toml
:
request_client_cert = true
Just for demonstrating this, give the following rule to user tutorial
in Keycloak:
HTTPS/# MQTT/# WS/#
Restart the Border Gateway and Redis for the change to take effect:
cd $BGW_HOME/test/tutorial
docker-compose -f docker-compose-tutorial.yml restart bgw-ssl redis
Now no connection to any of the services will be possible without providing a client certificate signed by $BGW_HOME/certs/CA.pem
. Here is how to provide an example client certificate for curl,
mosquitto_sub
and wscat
:
curl -v --cert $BGW_HOME/certs/client.pem --key $BGW_HOME/certs/client.key --cacert $BGW_HOME/certs/CA.pem -X PUT https://bgw-ssl:8443/sc/helloworld -d '{"name": "helloworld","description": "helloworld-express-app","apis": {"HTTP": "http://helloworld-express-app:8080"}, "ttl": 86400}' -H "accept: application/json" -H "Authorization: Basic dHV0b3JpYWw6dHV0b3JpYWw="
mosquitto_sub -h bgw-ssl -p 8883 -d -t 'tutorial' --cafile $BGW_HOME/certs/CA.pem --cert $BGW_HOME/certs/client.pem --key $BGW_HOME/certs/client.key -u tutorial -P tutorial
wscat --ca $BGW_HOME/certs/CA.pem --cert $BGW_HOME/certs/client.pem --key $BGW_HOME/certs/client.key --connect wss://bgw-ssl:9002/?basic_auth=dHV0b3JpYWw6dHV0b3JpYWw
Originally written by Mohammad Alhareeqi and Jannis Warnat.