A few months ago, I stumbled upon this list of 16 practices for securing your API:
- Certification: Verifies the identity of users accessing APIs.
- Authorization: Specifies the permissions of authenticated users.
- Data editing: Hides sensitive data for protection.
- Encryption: It encodes data so that only authorized parties can decode it.
- Troubleshooting: Manages responses when things go wrong, avoiding disclosure of sensitive information.
- Data entry validation and correction: It checks the input data and removes harmful parts.
- Intrusion detection systems: Monitor networks for suspicious activity.
- List of allowed IP addresses: Allows API access only from trusted IP addresses.
- Recording and monitoring: Keeps detailed logs and regularly monitors APIs.
- Speed limit: Limits user requests to prevent overload.
- Safe dependencies: It ensures that there are no third-party vulnerabilities.
- Security headers: Improves website security against types of attacks such as XSS.
- Token Expiration: Regularly expiring and renewing tokens prevent unauthorized access.
- Using security standards and frameworks: It guides your API security strategy.
- Web application firewall: Protects your site from HTTP-specific attacks.
- API versions: It maintains different versions of your API for seamless updates.
Although it is debatable whether some points refer to safety, for example versions, the list is a good starting point anyway. In this two-post series, I’d like to describe how we can implement each point with Apache APISIX (or not).
Certification
Authentication is about identifying yourself with the system. Proof is needed.
Apache APISIX provides two types of authentication: internal, with APISIX to verify credentials, and external, when delegated to a third party. All authentication mechanisms work through plugins. Here is the current list of available authentication plugins.
Type | Name | Description |
---|---|---|
Internal | key-auth |
Authentication via HTTP headers |
basic-auth |
It relies on a browser callback | |
jwt-auth |
It uses a JWT token for authentication | |
External | authz-keycloak |
Delegates to Keycloak |
authz-casdoor |
Delegates at Casdoor | |
wolf-rbac |
Delegates pull | |
openid-connect |
It delegates to a third party that is compatible with OpenID Connect | |
cas-auth |
Delegates to a CAS compliant third party | |
hmac-auth |
Delegates to an HMAC compliant third party | |
authz-casbin |
It delegates to a third party that is compatible with Lua Casbin | |
ldap-auth |
Delegates to LDAP | |
opa |
Delegates the Open Policy Agent endpoint | |
forward-auth |
Forwards authentication to a third-party endpoint |
APISIX grants authenticated calls a consumer. For example, we can create a consumer authenticated with key-auth
include:
consumers:
- username: john
plugins:
key-auth:
key: mykey
Any request that contains a header apikey
with a key mykey
will be assigned to the consumer john
.
Authorization
Authentication alone is not enough. Once the URL request has been validated, we need to decide if it is allowed to continue. That is the role of authority.
Authorization […] is the function of determining the rights/privileges of access to resources, which refers to general information security and computer security, and in particular to access control. More formally, “authorize” means to define an access policy.
— Authorization on Wikipedia
Apache APISIX enforces authorization mainly through the consumer restriction plugin. Here is the simplest use consumer-restriction
include:
consumers:
- username: johndoe #1
plugins:
keyauth:
key: mykey
routes:
- upstream_id: 1 #2
plugins:
keyauth: ~
consumer-restriction:
whitelist: #3
- johndoe
- Define the consumer
- Referencing a pre-existing upstream
- It allows only defined consumers to access the route
Most real-world authorization models avoid tying an identity directly to a license. They generally bind a group (and even a role) so that it becomes easier to manage many identities. Apache APISIX provides a consumer pool abstraction for this.
consumer_groups:
- id: accountants #1
consumers:
- username: johndoe
group_id: accountants #2
plugins:
keyauth:
key: mykey
routes:
- upstream_id: 1
plugins:
keyauth: ~
consumer-restriction:
type: consumer_group_id #3
whitelist:
- accountants
- Define the consumer group
- Assign the consumer to a previously defined group of consumers
- Restrict access to members of a defined group of consumers, i.e,
accountants
Input validation
With Apache APISIX, you can define a set of JSON schemas and validate a request against any of them. My colleague Navendu wrote a comprehensive blog post on the topic: Your API requests should be validated.
I don’t think API Gateway is responsible for processing requests. Each upstream has specific logic, and moving the responsibility for validation from the upstream to the Gateway binds the latter to logic with no real benefit.
Either way, the checkbox is checked.
List of allowed IP addresses
Apache APISIX implements IP whitelisting via the ip-limit plugin. You can define common IP addresses or CIDR blocks.
routes:
- upstream_id: 1
plugins:
ip-restriction:
whitelist:
- 127.0.0.1
- 13.74.26.106/24
Logging and tracking
Logging and monitoring fall into the broader Noticeability category, which also includes Tracing. Apache APISIX offers a wide range of monitoring plugins in each category.
Type | Name | Description |
---|---|---|
Tracing | zipkin |
Collect and send traces according to the Zipkin specification |
skywalking |
Integrate with the Apache SkyWalking project | |
opentelemetry |
Report data according to the OpenTelemetry specification | |
Metrics | prometheus |
Expose metrics in Prometheus format |
node-status |
Expose metrics in JSON format | |
datadog |
Integrate with Datadog | |
Cutting wood | file-logger |
Push log streams to a local file |
syslog |
Push logs to Syslog server | |
http-logger |
Push JSON encoded logs to HTTP server | |
tcp-logger |
Push JSON encoded logs to TCP server | |
udp-logger |
Push JSON encoded logs to UDP server | |
kafka-logger |
Push JSON encoded logs to Kafka cluster | |
rocketmq-logger |
Push JSON-encoded records to the RocketMQ cluster | |
loki-logger |
Push JSON-encoded logs to a Loki instance | |
splunk-hec-logging |
Push logs to a Splunk instance | |
loggly |
Push logs to a Loggly instance | |
elasticsearch-logger |
Push the logs to the Elasticsearch instance | |
sls-logger |
Push logs to Alibaba Cloud Log Service | |
google-cloud-logging |
Push access logs to Google Cloud Logging Service | |
tencent-cloud-cls |
Push access logs to Tencent Cloud CLS |
Rate limit
Rate limiting protects upstream channels from Distributed Denial of Services attacks, aka DDoS. This is one of the main features of reverse proxies and API gateways. APISIX implements rate limiting through three different plugins:
- The limit-conn plugin limits the number of concurrent requests to your services
- The limit-req plugin limits the number of requests to your service using the leaky bucket algorithm
- A capping plugin limits the number of requests to your service to a specific number at a time. Supplement uses Fixed window algorithm
Let’s take advantage limit-count
for example:
routes:
- upstream_id: 1
plugins:
limit-count:
count: 10
time_window: 1
rejected_code: 429
The above configuration snippet protects the upstream from more than ten requests per second. It is applied to every IP address due to the default configuration. The complete snippet would look like this:
routes:
- upstream_id: 1
plugins:
limit-count:
count: 10
time_window: 1
rejected_code: 429
key_type: var
key: remote_addr
When working with APIs, chances are you want to differentiate your customers. Some might get a better price for different reasons: they paid a premium offer, are considered strategic, are internal clients, etc. The same consumer can also use different IP addresses because they are running on different machines with different APIs. It would be unfair to allow the same user to make multiple calls because they are executing their requests on a distributed infrastructure.
As it stands, IP is not a good way to assign restrictions; we prefer to use a named consumer or, even better, a group of consumers. With APISIX this is perfectly possible:
consumer_groups:
- id: basic
plugins:
limit-count:
count: 1
time_window: 1
rejected_code: 429
- id: premium
plugins:
limit-count:
count: 10
time_window: 1
rejected_code: 429
consumers:
- username: johndoe
group_id: basic
plugins:
keyauth:
key: mykey1
- username: janedoe
group_id: premium
plugins:
keyauth:
key: mykey2
routes:
- upstream_id: 1
plugins:
key-auth: ~
Now, johndoe
it can only send a request every second, because it is a part basic
to plan while janedoe
can claim ten times more as part of a premium plan.
Conclusion
We’ve seen how to configure Apache APISIX to protect your APIs from 7 of the 16 rules in the original list. The remaining rules may be less easy to implement; we’ll cover them in another installment.