HAProxy — PART I — Secure an API
In this article we will see how to secure an API with HAProxy.
Here, we provide an API by using PostgREST that is serving APIs for a PostgreSQL database.
Find all the project files here in our repo on GitHub.
To start, let’s provide a PostgreSQL database and a PostgREST to serve the API with docker-compose.yml :
version: "3"
services:
demo-secure-db:
restart: always
image: postgres:16.3-alpine3.20
environment:
- POSTGRES_USER=demo
- POSTGRES_PASSWORD=demo
- POSTGRES_DB=demo
demo-postgrest-api:
restart: always
image: postgrest/postgrest:latest
depends_on:
- demo-secure-db
environment:
- PGRST_DB_URI=postgres://demo:demo@demo-secure-db:5432/demo
- PGRST_DB_SCHEMA=public
- PGRST_DB_ANON_ROLE=demo
- PGRST_DB_SCHEMAS=public
ports:
- "3000:3000"
Now, let’s test the security level offered by default with PostgREST. To achieve this, we can use VulnAPI (https://vulnapi.cerberauth.com/docs/) to scan APIs for vulnerabilities and security risks.
We can use docker to run the test with VulnAPI:
docker run --rm --network haproxy-secure-api_default cerberauth/vulnapi scan curl http://demo-postgrest-api:3000
and this is the result :
+------------+--------------------------------+--------------------------------+
| RISK LEVEL | VULNERABILITY | DESCRIPTION |
+------------+--------------------------------+--------------------------------+
| Info | Operation Accepts | The operation accepts |
| | Unauthenticated Requests | unauthenticated requests or |
| | | the authenticated scheme has |
| | | not been detected. This can |
| | | lead to unauthorized access |
| | | and security issues. |
| Info | CSP Header is not set | No Content Security Policy |
| | | (CSP) Header has been detected |
| | | in HTTP Response. |
| Info | CORS Header is not set | No CORS Header has been |
| | | detected in HTTP Response. |
| Info | HSTS Header is not set | No HSTS Header has been |
| | | detected in HTTP Response. |
| Info | X-Content-Type-Options Header | No X-Content-Type-Options |
| | is not set | Header has been detected in |
| | | HTTP Response. |
| Info | X-Frame-Options Header is not | No X-Frame-Options Header |
| | set | has been detected in HTTP |
| | | Response. |
| Info | Server Signature Exposed | A Server signature is exposed |
| | | in an header. |
+------------+--------------------------------+--------------------------------+
As we can see, some headers are missing to secure the API. By default, the server signature is exposed in the “server” header.
To secure the PostgREST API, let’s use HAProxy.
To begin, it is recommended to read the OWASP project best practises to secure our APIs here : https://cheatsheetseries.owasp.org/cheatsheets/REST_Security_Cheat_Sheet.html
In the HAProxy configuration at the backend section serving the API, we just need to add some set-header directives to fit the OWASP best practises :
# by default this headers are mandatory
http-response set-header Content-Security-Policy "frame-ancestors 'none'"
http-response set-header X-Content-Type-Options "nosniff"
http-response set-header X-Frame-Options "DENY"
http-response set-header Cache-Control "no-store"
# in a production environment, providing https access, you should add this header
http-response set-header Strict-Transport-Security "max-age=16000000; includeSubDomains; preload;"
# let's say the api is a public api, so CORS directive is permissive
http-response set-header Access-Control-Allow-Origin "*"
We also need to delete the server header :
http-response del-header Server
Now, let’s see what kind of security risks have been discovered by VulnAPI now by running :
docker run --rm --network haproxy-secure-api_default cerberauth/vulnapi scan curl http://haproxy-secure-api:8080
+------------+--------------------------------+--------------------------------+
| RISK LEVEL | VULNERABILITY | DESCRIPTION |
+------------+--------------------------------+--------------------------------+
| Info | Operation Accepts | The operation accepts |
| | Unauthenticated Requests | unauthenticated requests or |
| | | the authenticated scheme has |
| | | not been detected. This can |
| | | lead to unauthorized access |
| | | and security issues. |
| Info | CORS Header is set but | CORS Header has been detected |
| | permissive | in HTTP Response but is |
| | | permissive. |
+------------+--------------------------------+--------------------------------+
Everything seams to be ok now !!!
We have a public API which is unauthenticated and a CORS permissive header to allow its use by any domain name.
In a production environment make sure to :
- restrict the use of the API to our domain name (restrict the CORS Header);
- use https (served by HAProxy)
- authenticate all our APIs
See part II if you enjoyed part I :
https://medium.com/@nexsol-tech/haproxy-part-ii-caching-api-with-haproxy-f831f8fc9e92