The goal of this project is to create a simple REST API and securing it with Kong using the
LDAP Authentication and Basic Authentication plugins. Besides, we will explore more plugins that Kong offers like:
Rate Limiting, StatsD and Prometheus plugins.
Spring-Boot Java Web application that exposes two endpoints:
/api/public: endpoint that can be access by anyone, it is not secured;/api/private: endpoint that must be accessed only by authenticated users.
In springboot-kong root folder, run
./mvnw clean package dockerfile:build -DskipTests --projects simple-service
Run the following script present in springboot-kong project root folder.
./start-docker-containers.sh
simple-serviceapplication is running in a docker container. The container does not expose any port to HOST machine. So, the application cannot be accessed directly, forcing the caller to use ofKongas gateway server to access it.
The LDIF file that we will use, springboot-kong/ldap/ldap-mycompany-com.ldif, has already a pre-defined structure for
mycompany.com. Basically, it has 2 groups (developers and admin) and 4 users (Bill Gates, Steve Jobs, Mark Cuban
and Ivan Franchin). Besides, it is defined that Bill Gates, Steve Jobs and Mark Cuban belong to developers group
and Ivan Franchin belongs to admin group.
Bill Gates > username: bgates, password: 123
Steve Jobs > username: sjobs, password: 123
Mark Cuban > username: mcuban, password: 123
Ivan Franchin > username: ifranchin, password: 123
There are two ways to import those users: just running a script or through phpldapadmin
In a new terminal, inside springboot-kong root folder run
./import-openldap-users.sh
-
Access the link: https://localhost:6443
-
Login with the credentials
Login DN: cn=admin,dc=mycompany,dc=com
Password: admin
- Import the file
springboot-kong/ldap/ldap-mycompany-com.ldif
In a terminal, you can test ldap configuration using ldapsearch
ldapsearch -x -D "cn=admin,dc=mycompany,dc=com" \
-w admin -H ldap://localhost:389 \
-b "ou=users,dc=mycompany,dc=com" \
-s sub "(uid=*)"
Note. In order to run some commands/scripts, you must have jq installed on you
machine
Before adding to Kong Services, Routes and Plugins, check if Kong it's running
curl -I http://localhost:8001
Using application/x-www-form-urlencoded content type
curl -i -X POST http://localhost:8001/services/ \
-d "name=simple-service" \
-d "protocol=http" \
-d "host=simple-service" \
-d "port=8080"
OR
You can use application/json content type. Besides, in order to set protocol, host, port and path at once,
the url shorthand attribute can be used.
curl -i -X POST http://localhost:8001/services/ \
-H 'Content-Type: application/json' \
-d '{ "name": "simple-service", "url":"http://simple-service:8080" }'
- One default route for the service, no specific
pathincluded
PUBLIC_ROUTE_ID=$(curl -s -X POST http://localhost:8001/services/simple-service/routes/ \
-d "protocols[]=http" \
-d "hosts[]=simple-service" | jq -r '.id')
echo "PUBLIC_ROUTE_ID=$PUBLIC_ROUTE_ID"
- Another route specifically for
/api/privateendpoint (it will be secured and only accessible by LDAP users)
PRIVATE_ROUTE_ID=$(curl -s -X POST http://localhost:8001/services/simple-service/routes/ \
-H 'Content-Type: application/json' \
-d '{ "protocols": ["http"], "hosts": ["simple-service"], "paths": ["/api/private"], "strip_path": false }' | jq -r '.id')
echo "PRIVATE_ROUTE_ID=$PRIVATE_ROUTE_ID"
- Finally, one route for
/actuator/httptraceendpoint (it will be secured and only accessible by pre-defined users)
HTTPTRACE_ROUTE_ID=$(curl -s -X POST http://localhost:8001/services/simple-service/routes/ \
-H 'Content-Type: application/json' \
-d '{ "protocols": ["http"], "hosts": ["simple-service"], "paths": ["/actuator/httptrace"], "strip_path": false }' | jq -r '.id')
echo "HTTPTRACE_ROUTE_ID=$HTTPTRACE_ROUTE_ID"
In order to list all
simple-serviceroutes, run:curl -s http://localhost:8001/services/simple-service/routes | jq .
/api/publicendpoint
curl -i http://localhost:8000/api/public -H 'Host: simple-service'
It should return
HTTP/1.1 200
It is public.
/api/privateendpoint
curl -i http://localhost:8000/api/private -H 'Host: simple-service'
It should return
HTTP/1.1 200
null, it is private.
PS. this endpoint is not secured by the application, that is why the response is returned. The idea is to use Kong to secure it. It will be done on the next steps.
/actuator/httptraceendpoint
curl -i http://localhost:8000/actuator/httptrace -H 'Host: simple-service'
It should return
HTTP/1.1 200
{"traces":[{"timestamp":"...
PS. again, as happened previously with /api/private, /actuator/httptrace endpoint is not secured by the application.
We will use Kong to secure it on the next steps.
In this project, we are going to add those plugins: LDAP Authentication, Rate Limiting, StatsD and Basic Authentication.
Please refer to https://konghq.com/plugins for more plugins.
The LDAP Authentication plugin will be used to secure the /api/private endpoint.
- Add plugin to route
PRIVATE_ROUTE_ID
LDAP_AUTH_PLUGIN_ID=$(curl -s -X POST http://localhost:8001/routes/$PRIVATE_ROUTE_ID/plugins \
-d "name=ldap-auth" \
-d "config.hide_credentials=true" \
-d "config.ldap_host=ldap-host" \
-d "config.ldap_port=389" \
-d "config.start_tls=false" \
-d "config.base_dn=ou=users,dc=mycompany,dc=com" \
-d "config.verify_ldap_host=false" \
-d "config.attribute=cn" \
-d "config.cache_ttl=60" \
-d "config.header_type=ldap" | jq -r '.id')
echo "LDAP_AUTH_PLUGIN_ID=$LDAP_AUTH_PLUGIN_ID"
If you need to update some
LDAP Authenticationplugin configuration, run the followingPATCHcall informing the field you want to update, for example:curl -X PATCH http://localhost:8001/plugins/${LDAP_AUTH_PLUGIN_ID} \ -d "config.base_dn=ou=users,dc=mycompany,dc=com"
- Try to call
/api/privateendpoint without credentials.
curl -i http://localhost:8000/api/private -H 'Host: simple-service'
It should return
HTTP/1.1 401 Unauthorized
{"message":"Unauthorized"}
- Call
/api/privateendpoint using Bill Gates base64 encode credentials
curl -i http://localhost:8000/api/private \
-H "Authorization:ldap $(echo -n 'Bill Gates':123 | base64)" \
-H 'Host: simple-service'
It should return
HTTP/1.1 200
Bill Gates, it is private.
The Basic Authentication plugin will be used to secure the /actuator/httptrace endpoint
- Add plugin to route
HTTPTRACE_ROUTE_ID
BASIC_AUTH_PLUGIN_ID=$(curl -s -X POST http://localhost:8001/routes/$HTTPTRACE_ROUTE_ID/plugins \
-d "name=basic-auth" \
-d "config.hide_credentials=true" | jq -r '.id')
echo "BASIC_AUTH_PLUGIN_ID=$BASIC_AUTH_PLUGIN_ID"
- Try to call
/actuator/httptraceendpoint without credentials.
curl -i http://localhost:8000/actuator/httptrace -H 'Host: simple-service'
It should return
HTTP/1.1 401 Unauthorized
{"message":"Unauthorized"}
- Create a consumer
IFRANCHIN_CONSUMER_ID=$(curl -s -X POST http://localhost:8001/consumers -d "username=ivanfranchin" | jq -r '.id')
echo "IFRANCHIN_CONSUMER_ID=$IFRANCHIN_CONSUMER_ID"
- Create a credential for consumer
IFRANCHIN_CREDENTIAL_ID2=$(curl -s -X POST http://localhost:8001/consumers/ivanfranchin/basic-auth \
-d "username=ivan.franchin" \
-d "password=123" | jq -r '.id')
echo "IFRANCHIN_CREDENTIAL_ID2=$IFRANCHIN_CREDENTIAL_ID2"
- Call
/api/privateendpoint usingivan.franchincredential
curl -i -u ivan.franchin:123 http://localhost:8000/actuator/httptrace -H 'Host: simple-service'
It should return
HTTP/1.1 200
{"traces":[{"timestamp":"...
- Let's create another consumer just for testing purpose
ADMINISTRATOR_CONSUMER_ID=$(curl -s -X POST http://localhost:8001/consumers -d "username=administrator" | jq -r '.id')
echo "ADMINISTRATOR_CONSUMER_ID=$ADMINISTRATOR_CONSUMER_ID"
ADMINISTRATOR_CREDENTIAL_ID=$(curl -s -X POST http://localhost:8001/consumers/administrator/basic-auth \
-d "username=administrator" \
-d "password=123" | jq -r '.id')
echo "ADMINISTRATOR_CREDENTIAL_ID=$ADMINISTRATOR_CREDENTIAL_ID"
We are going to add the following rate limitings:
/api/public: 1 request a second;/api/private: 5 requests a minute;/actuator/httptrace: 2 requests a minute or 100 requests an hour.
Let's set them.
- Add plugin to route
PUBLIC_ROUTE_ID
PUBLIC_RATE_LIMIT_PLUGIN_ID=$(curl -s -X POST http://localhost:8001/routes/$PUBLIC_ROUTE_ID/plugins \
-d "name=rate-limiting" \
-d "config.second=1" | jq -r '.id')
echo "PUBLIC_RATE_LIMIT_PLUGIN_ID=$PUBLIC_RATE_LIMIT_PLUGIN_ID"
- Add plugin to route
PRIVATE_ROUTE_ID
PRIVATE_RATE_LIMIT_PLUGIN_ID=$(curl -s -X POST http://localhost:8001/routes/$PRIVATE_ROUTE_ID/plugins \
-d "name=rate-limiting" \
-d "config.minute=5" | jq -r '.id')
echo "PRIVATE_RATE_LIMIT_PLUGIN_ID=$PRIVATE_RATE_LIMIT_PLUGIN_ID"
- Add plugin to route
HTTPTRACE_ROUTE_ID
HTTPTRACE_RATE_LIMIT_PLUGIN_ID=$(curl -s -X POST http://localhost:8001/routes/$HTTPTRACE_ROUTE_ID/plugins \
-d "name=rate-limiting" \
-d "config.minute=2" \
-d "config.hour=100" | jq -r '.id')
echo "HTTPTRACE_RATE_LIMIT_PLUGIN_ID=$HTTPTRACE_RATE_LIMIT_PLUGIN_ID"
- Make some calls those endpoints
- Test
/api/public
curl -i http://localhost:8000/api/public -H 'Host: simple-service'
curl -i http://localhost:8000/actuator/health -H 'Host: simple-service'
- Test
/actuator/httptrace
curl -I -u ivan.franchin:123 http://localhost:8000/actuator/httptrace -H 'Host: simple-service'
curl -I -u administrator:123 http://localhost:8000/actuator/httptrace -H 'Host: simple-service'
- Test
/api/private
curl -i http://localhost:8000/api/private \
-H "Authorization:ldap $(echo -n 'Bill Gates':123 | base64)" \
-H 'Host: simple-service'
curl -i http://localhost:8000/api/private \
-H "Authorization:ldap $(echo -n 'Mark Cuban':123 | base64)" \
-H 'Host: simple-service'
P.S. The rate limiting is the same for Bill Gates and Mark Cuban! That's wrong!
- After exceeding some calls in a minute, you should see
HTTP/1.1 429 Too Many Requests
{"message":"API rate limit exceeded"}
- Add plugin to
simple-service
GRAPHITE_STATSD_PLUGIN_ID=$(curl -s -X POST http://localhost:8001/services/simple-service/plugins \
-d "name=statsd" \
-d "config.host=graphite-statsd" \
-d "config.port=8125" | jq -r '.id')
echo "GRAPHITE_STATSD_PLUGIN_ID=$GRAPHITE_STATSD_PLUGIN_ID"
-
Make some requests to
simple-serviceendpoints -
Access
Graphite-Statsdat http://localhost:8081 and check thekongstatistics.
- Add plugin to
simple-service
GRAPHITE_STATSD_PLUGIN_ID=$(curl -s -X POST http://localhost:8001/services/simple-service/plugins \
-d "name=prometheus" | jq -r '.id')
echo "GRAPHITE_STATSD_PLUGIN_ID=$GRAPHITE_STATSD_PLUGIN_ID"
- You can see some metrics
curl -i http://localhost:8001/metrics

