APISIX: Authentication, security and traffic

In Introduction to Apache APISIX we studied the different components of this platform and the advantages of using it. In addition, we saw a way to dockerise it and we configured a simple route with the openid-connect plugin, integrating authentication through Auth0.

In this post, we are going to work with some plugins that will allow us to add a layer of security and customisation to our routes. We will look at two ways to require authentication, a procedure to control the traffic of incoming requests and a mechanism to control how HTTP requests are handled.

Consumer and Consumer group in APISIX

There are occasions in which we will need to apply a series of restrictions or specific configurations to a user or client. For this, APISIX provides an object called consumer with which we will be able to define these specifications. Throughout this post, some examples will be shown.

The elements that we will use to configure a consumer are the username and the plugins that will be consumed by the route that requires it. For each consumer, each of the plugins that we configure in it may be focused on solving a different need from the rest (as in the case of authentication and security) or, on the contrary, complement each other to work on a common need (for example, several plugins based on traffic control).

On the other hand, while we can enable plugins in different parts of the API Manager configuration (such as in the route, in the service or in the consumer), the highest priority is set in the consumer, so if we configure one for a specific user or client, whatever we have defined in the consumer will be applied.

Other times, we will need to use what are called consumer groups, which are used to extract commonly used plugin configurations (not focused on a single specific path) and can be linked directly to a consumer. By using them, we can define any number of plugins and apply them to different consumers. However, it should be noted that any configuration that is set on the consumer, even if it is on a plugin that already comes from a consumer group applied to that consumer, will override and overwrite the configuration.

Authentication: Access Control Policies

One of the quickest and easiest ways to add a layer of security to our applications is by authenticating the users or clients that can access them. To get an idea of how we can implement this with APISIX, we are going to work with two plugins: basic-auth and key-auth.

basic-auth plugin

When we talk about authentication, the first thing that comes to mind are user credentials and passwords. This is called Basic Access Authentication and consists of using the HTTP header in the form ‘Authorization: Basic <credentials>’, where <credentials> is the Base64 encoding of ‘<user>:<password>’. This can be easily applied to our routes by creating a consumer with a username and a json with the basic-auth plugin we are going to use, entering the credentials as shown in the following example:

Once the consumer is created and the plugin is configured in it, we apply it to our route by simply specifying the plugin we want to use. It will take by default the configuration that has been defined in the consumer.

Now, we just need to send the credentials along with our request. For this, we have two ways to make the request, with the command curl -u <user>:<password> or with Authorization: Basic <credentials>. This is because curl -u (or curl --user) is used to make HTTP requests that require basic authentication, taking care of the process of encrypting the credentials in Base64 and sending them in the ‘Authorization’ header.

If, on the other hand, we do not indicate the credentials, the result we would obtain would be the following:

key-auth plugin

Another method to ensure that only authorised users can access certain routes is to use the key-auth plugin and assign a unique key to a route or service. This form of authentication is much simpler than the previous one, as it does not require a user or client associated with credentials. To apply it, we configure the consumer as follows:

This plugin has three customisable parameters:

  • header: Name of the header from which the key will be obtained.
  • query: The query string from which to obtain the key, although it has a lower priority than the header.
  • hide_credentials: Here we determine if the header of the request containing the authentication information will be passed or if it will be deleted before reaching the proxy.

We are going to work with the header, which by default has the name “apikey”, and we will give it a couple of different names to test it: one that could be reserved or one of a personal nature.

We test the header with the key as follows and get the expected result:

If we need to change the header name to a more customised one, we would simply specify the name in the plugin configuration in the path as follows: 

First, we check that the header name we had defined can no longer be used:

On the other hand, we use the new one and see that it works:

Up to this point, we have compared two relatively simple authentication plugins, focused on two different purposes. With basic-auth we were able to provide credentials to a specific user or client and with key-auth we were able to provide a commonly used key to secure access to a specific path in a flexible way thanks to the possibility of creating our own header. This would be very useful when combining different authentication methods that may require the use of the fairly standard ‘Authorisation’ header. 

Traffic: Configuration to control requests

limit-req plugin

To protect our services from being overloaded by excessive requests, we are going to implement the limit-req plugin, which is in charge of setting the limit of requests that can be made in a period of time. It is built using the leaky bucket algorithm, which is based on an analogy of a leaky bucket being filled with water. The bucket represents a server that is receiving requests (incoming drops of water) at a rate, while the processing of the requests (leaky bucket) is taking place.

The goal of the algorithm with the analogy would be to determine the optimal amount and speed of incoming requests that are allowed on the server based on its processing capacity so as not to reach the overflow.

Unlike the previous plugins, this one has a larger repertoire of configuration parameters. In order not to be too long, we are going to focus on the following:

  • rate: Limit of requests per second allowed.
  • burst: Number of additional requests allowed to be delayed per second.
  • reject_code: HTTP status code returned in case of exceeding the imposed limit.
  • key_type: Type of key to be used that is specified by the user.
  • key: Key on which the request limitation will be based. In this case, as it is ‘key_type’: ‘var’ instead of ‘var_combination’, the key will be treated as a variable name.

Next, we will give the following values to the plugin:

These values indicate the following characteristics:

  • rate: 1. Implies that only one request per second will be supported.
  • burst: 1. Only one request per second will be delayed so that it can be resolved when the server becomes free.
  • rejected_code: 503. An error code indicating that the service is not available at the time of the query.
  • key_type: var. Indicates that the limiting of the request shall depend on the use of a variable to be specified as a variable name defined by the parameter ‘key’.
  • key: remote_addr. States that the limiting shall be done on the basis of the remote IP address.

To test that we have configured it well, we recommend using several tabs of the console and make the request many times very quickly. Remember that we have a time of one second between requests, bearing in mind that the second request will wait for one second until it is answered and it is the third one that should give an error if we have done it fast enough. One tool that could be useful and fast is the Terminator console, which allows us to open many terminals in a single window, or if you prefer, you can also make a small script.

Below, we see the result of a request that did not reach the limit and one that did:

Now, we are going to take advantage of the logic of our consumer by changing the parameter ‘key: consumer_name’ to apply it. In this case, our consumer has the key-auth plugin defined with the configuration of the ‘Authorization’ header and the ‘cleverkey’ key.

Again, we try making requests inside and outside the boundary and get the following two results:

The usefulness of the configuration we have done with the limit-req plugin is to deal with the excessive use of requests that a client can make to pull down our website. On the other hand, it mitigates brute-force attacks and optimises costs for companies in cloud infrastructures. In this way, we protect the performance of our applications by maintaining a controlled flow of incoming requests.

Security: Establishing policies to protect routes

cors policies

To make resource requests to a web server from a domain other than the one from which the resource originates, we can apply CORS (Cross-Origin Resource Sharing) policies. In this way, we can indicate what specifications the origin must provide in its request: domain, schema, port or allowed methods. To do this, you can use the cors plugin, which allows you to define rules such as allowed methods and headers.

Like the plugin we used in the previous point, this one has a wide range of configuration parameters. In this case, we will use allow_methods (methods allowed for the HTTP protocol), allow_headers (headers allowed in the request) and max_age (maximum time in seconds for storing the result in the cache).

These values indicate the following characteristics:

  • allow_methods: GET. States that a request can only be made if the GET method of the HTTP protocol is used.
  • allow_headers: Content-Type: text/plain. Only the plain text header may be used in the request.
  • max_age: 1. The result will be cached for one second.

Next, we are going to make a request applying correctly the predefined rules and we see that we get the result successfully:

However, if we use a method of the HTTP protocol that is not allowed, such as PUT, the result we get will be ‘Method Not Allowed’:

On the other hand, if we apply an impermissible header, we will not be able to access the route either. In this case, APISIX will return the error ‘400 Bad Request’:

Conclusion

In this article we have worked with several of the Apache APISIX plugins to protect our routes from unauthorised access and malicious attacks. By doing so, we managed to improve the security, performance and smooth running of our applications.

However, for some of these plugins we have left out a very interesting set of configurable parameters. So I invite you to play around with them so that you can get more out of APISIX when configuring your routes.

I hope the content of this post has been useful for you!

So much for today’s post. If you found it interesting, we encourage you to visit the Software category to see similar articles and to share it in networks with your contacts. See you soon!
Vanessa Pradas
Vanessa Pradas
Articles: 10