Page tree
Skip to end of metadata
Go to start of metadata

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.

Set up aliases for localhost

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 

Clone the repository

git clone https://github.com/linksmart/border-gateway.git

Set an environment variable BGW_HOME  to the path of the cloned repository.

Start up the OpenID Connect provider


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.

Start up the backend

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.

Start up the Border Gateway

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.

Performing HTTP methods

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 here):

[...]    
# 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 foward 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>"

Address translation

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. 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. 

Accessing the MQTT broker

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

Connection via WebSocket

We can use the tool 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.

Authorization rules

HTTP

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.

MQTT

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.

Using client certificates

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
  • No labels