In our modern world, web applications are becoming ever more important. Bad actors know this and they target them more frequently than ever before. This is not likely to stop any time soon as the number of web applications the world needs will only go up with its reliance on technology. To fully prevent an attack is impossible but we need to try our hardest to do so and in our daily struggles in this field we have a weapon that’s known as “hardening”.

What is hardening

In the most basic sense of the word, hardening your server means to increase its defences but in practice there are so many ways in which a server can be vulnerable. One technique that is commonly used is to sanitise the user input in our application. User data might contain malicious code or unexpected input and we should take care in hardening our servers against this behaviour.

Another hardening technique that is being used quite often is what’s called javascript obfuscation. Web applications are no longer the static websites they used to be. They contain a lot of javascript and this javascript could reveal a lot of information to a potential hacker. To prevent this, the code is obfuscated so that the hacker will have a much harder time figuring out the applications internal workings.

 javascript obfuscation example
Javascript obfuscation example

Firewalls on the packet level have been implemented for ages in production environments but recently Web Application Firewalls are gaining ground fast. They will inspect any http request and depending on the configuration, they might report or block any call that contains unexpected or malicious input. This is just one tool in the tool belt of the modern system administrator but it’s a very important one.

Hardening vs not hardening

Hardening your web applications comes with a cost as do all good things in life. A more hardened server will be more secure from attackers, but it will also be slower as all of these security measures come with a cost. They will require more processing time of the system as a whole and of its components and this might significantly slow down an application.

For this exact reason it’s often a balance between implementing too little hardening and implementing too much.

Meet API firewall

API Firewall, is a light-weighted API Firewall to protect your API endpoints in cloud-native environments with API Schema validation. API Firewall relies on a positive security model allowing calls that match predefined API specs, while rejecting everything else.

Technically, API Firewall is a reverse proxy with a built-in OpenAPI v3 request and response validator, written in Go, and optimised for extreme performance and near-zero added latency.

Pre-requisites

To install the API Firewall we have a couple of pre-requisites that need to be satisfied.

Installing the connection demo

To test our WAF we will be using the connexion demo provided by Zalando. We need to install and run this of course. We can do this with one easy command.

pip install connexion[swagger-ui]Later on we will start up connexion so we can test it with our API firewall.

Setting up

To get started we move into the /tmp directory before we clone the connexion repository.

cd /tmp
git clone <https://github.com/zalando/connexion>
Setting up 1

Next we need to pull the api-firewall docker file from the docker hub

docker pull wallarm/api-firewall

As a last step we can easily run the API firewall with the following command

docker run -d -v /tmp/connexion/examples/openapi3/methodresolver/openapi/:/tmp -e APIFW_SERVER_URL=http://178.79.152.114:9090/v1.0/ -e APIFW_API_SPECS=/tmp/pets-api.yaml -e APIFW_REQUEST_VALIDATION=BLOCK -e APIFW_RESPONSE_VALIDATION=BLOCK  -p 8282:8282 wallarm/api-firewall

This will start up our docker container in the background and give us the identifier of the docker container.

setting up 2

Starting up the connexion python app is up next because we need to have something to test on. We can do this by moving into the directory “methodresolver” and using python3 to start the app.

cd /tmp/connexion-master/examples/openapi3/methodresolver/
python3 app.py

You can check out the URL that it took up in the logs.

Now we need to test that everything works using curl. We will try to make a PUT request to update some data and see if it works. We have to execute this request from an external computer because we will be talking to the public facing IP address of the server. Make sure you replace our IP address with yours.

curl -X PUT -H 'content-type: application/json' -d '{"name":"homyak-2", "tag":"aa"}' <http://178.79.152.114:8282/v1.0/pets/3>

This should return the following output.

{
  "id": 3,
  "last_updated": "2021-06-05T19:21:35.399530Z",
  "name": "homyak-2",
  "tag": "aa"
}
Setting up 3

Now that we have a working API framework and a working API firewall, we can try to send out a malicious request.

curl -X PUT -H 'content-type: application/json' -d '{"name":111, "tag":"aa"}' <http://178.79.152.114:8282/v1.0/pets/3>

This will return a 403 error response.

To check this on the server we can go back and check the logs of our docker container with the command “docker logs CONTAINERID” where the containerID is the one we got from running our “docker run” command.

If you lost this ID, that is not a problem, you can check the running docker containers with the “docker container ls”. Next we can run the “docker logs” command to view our logs and see that the API firewall is filtering out the requests and blocking them.

The nitty-gritty details

Let’s talk a little bit about what we are doing here. First of all we are downloading connexion which is a demo application that will help us test our API firewall. We need to install it via python’s pip in order to run it as well.

After pulling our docker container we are starting up our firewalls on port 8282 using our docker container. The following command was used.

docker run -d -v /tmp/connexion/examples/openapi3/methodresolver/openapi/:/tmp -e APIFW_SERVER_URL=http://178.79.152.114:9090/v1.0/ -e APIFW_API_SPECS=/tmp/pets-api.yaml -e APIFW_REQUEST_VALIDATION=BLOCK -e APIFW_RESPONSE_VALIDATION=BLOCK  -p 8282:8282 wallarm/api-firewall

In it we use the following flag to indicate port 8282 should be forwarded.

-p 8282:8282

We also use the flag -e APIFW_SERVER_URL=http://178.79.152.114:9090/v1.0/ to indicate where our webserver is running at. This web server will be made up of the pets example from the connexion demo by later navigating to “/tmp/connexion-master/examples/openapi3/methodresolver/” and starting the app with “python3 app.py“. Of course you need to replace the IP adress with the ip address of your own server later on and don’t forget to change the port. You can either host your web server on the same server as the API-firewall or on a different server, in our case we will be running on the same server.

The “-e APIFW_REQUEST_VALIDATION=BLOCK -e APIFW_RESPONSE_VALIDATION=BLOCK ” flags ensure that requests that do not pass the validation rules get blocked.

The -v parameter mounts the “/tmp/connexion/examples/openapi3/methodresolver/openapi/” folder on our local drives to the “/tmp” folder on the docker container.

Finally the -d flag ensures that the docker container will run in the background. 

One thing I want to talk about as well is that “-e” flags in docker indicate an environment variable.Now that we are protecting our application, we need to start it as well. When we go to /tmp/connexion/examples/openapi3/methodresolver/ and run “python3 app.py“, that’s exactly what we do. This starts a webserver on port 9090 and serves the API.

Setting up 5

Now we have a working system of an API firewall that filters the traffic going to our API. In a real life scenario, we would now disable access to port 9090 from an outside network and only allow traffic coming from the internal network.

Other environment variables

Besides the environment variables previously mentioned using the “-e” flag, we have several other options.

Main section:

APIFW_URL: "http://0.0.0.0:8081"  # listening protocol, address and port
APIFW_LOG_LEVEL: "DEBUG"  # could be INFO, ERROR, WARNING or DEBUG
APIFW_READ_TIMEOUT: "5s"
APIFW_WRITE_TIMEOUT: "5s"
APIFW_SHUTDOWN_TIMEOUT: "5s"
APIFW_REQUEST_VALIDATION: "BLOCK" # could be BLOCK, LOG_ONLY or DISABLE
APIFW_RESPONSE_VALIDATION: "BLOCK" # could be BLOCK, LOG_ONLY or DISABLE

TLS section

APIFW_TLS_CERTS_PATH: "/api-firewall/resources/certs"
APIFW_TLS_CERT_FILE: "localhost.crt"
APIFW_TLS_CERT_KEY: "localhost.key"

OpenAPI Specification section

API-Firewall supports only OpenAPI spec v3. The file may have a JSON or YAML extension.

APIFW_API_SPECS: "/api-firewall/resources/swagger.json"

Server section

This section configures an API backend that the API Firewall protects.

APIFW_SERVER_URL: "http://example.com/v1/"
APIFW_SERVER_MAX_CONNS_PER_HOST: 512
APIFW_SERVER_READ_TIMEOUT: "5s"
APIFW_SERVER_WRITE_TIMEOUT: "5s"
APIFW_SERVER_DIAL_TIMEOUT: "200ms"

Conclusion

After working with API firewall and installing it we have seen it’s ease of installation and customisation. We can easily set up what api we want to monitor and what we want to do in case of a positive identification of malicious code. All of this is free without limitations which makes it all the better as some WAF solutions can become quite expensive.

Being that API firewall is delivered in a docker container, we can easily deploy this container into a kubernetes cluster with the helm chart we can find over at https://github.com/wallarm/api-firewall/tree/main/helm/api-firewall or using the docker-compose option of which we can find a demo over at https://github.com/wallarm/api-firewall/tree/main/demo/docker-compose. I want to conclude with a call to action, if you have any improvements please do not hesitate to make a pull request to that same repository over at https://github.com/wallarm/api-firewall. The fact these configurations are open source give me even more confidence in their operation and I hope I can inspire some of you to investigate the configuration and possibly even create your own pull requests.