Protecting Server Resources Hosting Unauthenticated APIs
In this article, I am going to build up a security paradigm for APIs that is flexible and can address a wide variety of use cases. We’re going to start at the beginning. The beginning of this discussion is protecting unauthenticated APIs.
Now, protecting an API isn’t necessarily any different from protecting any other computing resource. APIs are the latest way to solve a problem that has been around for sometime: how to get component A to talk to component B. So, if this is true, securing a publicly-facing API shouldn’t be that conceptually different from securing any other publicly-facing computational resource.
By conceptually, I mean the same basic questions need to be answered regardless of what is being protected. Any security model that protects a computing resource must address the following concepts:
Authentication is the process of proving your identity to the system.
Authorization is the process of determining if the authenticated identity is allowed to access the requested server resource based upon a predefined authorization policy.
Confidentiality is limiting access to the data to parties that should be allowed to see it. This could refer to the data in transit or at rest depending on the situation. It generally involves the use of some type of encryption technology such as Transport Layer Security (TLS).
Integrity ensures that the message was not modified in transit between two actors.
Non-Repudiation is assurance that the actor who sent the message cannot deny that it sent it.
Availability is a measure of the system performing as required. Most readers will be familiar with the concept of High Availability, which is having a high measure of the system performing as required.
It may very well be that the answer to one or more of these questions is simply that it is not needed. That is a perfectly legitimate answer assuming the proper level of analysis was done to arrive at that answer.
The answer to the first five of these is driven largely by the data classification attached to the data associated with the API. The last one is driven by how important it is to keep the system online. One may desire to keep the API up, running, and performing regardless of whether the data is public or private.
What do I mean by “unauthenticated”? This could refer to the authentication of the end-user. It could refer to the authentication of the client system making the API request. I commonly refer to the latter as the API Consumer. System could mean device, computer, VM, JVM, Docker container, or a couple of other things — those details are for another article. To keep things simple, let’s assume that neither the client system nor the end-user is being authenticated. This is common with native mobile apps or single page responsive apps making calls to public APIs that do not require knowing the identity of the user, application or the device. This was the very situation I was dealing with at a client site recently.
Furthermore, to have this situation at all, we must be dealing with an API that serves up public data that is in no way sensitive. It is possible for public data to have value (for example, if economic resources were spent to gather the data), at which point, allowing anyone to access it without paying for it, for example, may not be what the owner of the data intended. At which point, providing unauthenticated access is not the way you want to go. Data classification strategies is a topic for another blog entry. For now, let’s assume the data is meant for public consumption.
With that criteria, let’s evaluate the security model concepts:
|Authorization||None needed. Without authentication, one cannot make an authorization decision.|
|Confidentiality||None needed. If it is public data, there is no need to encrypt it at transport.|
|Integrity||None needed. There is usually no need to ensure that public data was not modified in transit between the actors. I suppose such a situation could exist, but let’s assume that’s not the case here.|
|Non-Repudiation||None needed. Likewise, no need for this.|
|Availability||This is a popular API and ability to run the business relies upon it being available at all times. Thus, Availability is very important.|
My experience with unauthenticated APIs at several organizations has involved similar lines of reasoning. So, generally, the only security criteria that must be addressed is Availability of the server that serves the API. Again, the same could be true for any unauthenticated resource.
For an API Gateway (or Enterprise Service Bus for that matter), that advertises numerous APIs that each have authentication and authorization requirements, there should be a standardized way of representing unauthenticated traffic to the system and of describing public APIs in authorization policy—more on that later.
So, availability of our publicly accessible API is the most important criteria of our security model. So, how do we maintain availability? The basic idea is to ensure that the amount of traffic coming in does not overwhelm the system hosting the API. The system must have:
- the ability to enforce some type of Service Level Agreement (SLA) for overall traffic throughput and individual clients;
- monitoring of system resource utilization (memory, cpu, disk I/O, network I/O);
- synthetic transactions to measure system availability externally;
- historical trending and analysis.
There are many products on the market capable of enforcing SLAs, such as IBM WebSphere DataPower appliances, Apigee Edge, and similar products. An SLA is a promise of a level of service (response time, availability measures, maybe other parameters) based upon certain constraints that the system will make the client adhere to. The constraints for our purposes will relate to the Transaction Per Second (TPS). The transaction count could be focused on individual clients or on the total. On DataPower, I have worked with clients that have two layers of Service Level Monitoring Actions (SLM, WebSphere DataPower’s functionality that implements what I am describing here) in the request processing. The first SLM Action enforces a client-side policy that stipulates that no more than 1 transaction can come in from each unique source IP every ten seconds. The second SLM Action enforces a server-side policy that only allows a global transaction count of 1000 TPS and no more than 800 outstanding calls to the API Provider. These numbers would be based upon the number of clients, the processing resources available on the API Provider, average response time of each API call, and other factors.
There is also an assumption that the system can see the source IP address of each client. For example, if your organization uses one-armed load balancer VIPs (Virtual IPs, load balancer farms), a side effect of that strategy is that the source IP of all requests hitting the thing behind the load balancer is that the source IP appears to be the load balancer VIP. If the load balancer is terminating the incoming TCP connections and establishing new connections to the backend servers, the IP address can be inserted in an HTTP Request Header. But, maybe the public APIs are hosted on a system with APIs that do require authentication and work with sensitive data. Then, TLS is required. Maybe the system also requires TLS to be terminated on the backend server so that server can manage the trust and validation of client certificates. This is the situation I was dealing with. So, there were no source IP addresses to work with—certainly made the task at hand more difficult.
A similar situation exists in Apigee Edge Public Cloud. The AWS Elastic Load Balancer (ELB) sitting in front of the Message Processors obfuscates the source IP address of incoming API requests if a custom virtual host is being used. This situation would arise whenever a custom TLS certificate needs to be presented rather than the default *.apigee.net certificate that is used. This situation can be worked around, but requires the use of SNI by all clients during the SSL/TLS handshake. Both of these examples bring have many variables that have to line up right to create the situation, but it happens. Drop me a message if you want more information.
The system needs to be monitored for memory, CPU, and I/O utilization. Alerts based upon predetermined thresholds need to exist to alert system administrators when those thresholds are exceeded. This may not help if there is a sudden, unexpected spike in traffic from a particular client or from many clients; that type of event is best deal with via the SLA enforcement functionality I mentioned above. The topic of monitoring API performance and availability is lengthy and will be covered in detail in another blog post, but I’ll outline one strategy below – a synthetic transaction.
A synthetic transaction is an active monitoring strategy that makes a test call from a monitoring platform into a live system and measures various aspects of the response. These aspects could be overall response time, the contents of the response, where the call is routed to, etc. – just about anything that is of interest to the group responsible for maintaining availability. This test call and the data fed into it will not harm or modify production data and should generally consist of read-only operations. If an update operation needs to be tested in this fashion, ensure that the operation is idempotent. Metrics gathered from synthetic transactions should be fed into a system that also contains metrics gathered by the live system.
It’s also possible to setup auto-scaling on some type of cloud-hosted solution that can handle very large volumes of traffic, but this may become prohibitively expensive for all but the largest organizations, depending on what level of financial resources you have available to throw at the problem and just how much volume is being considered. This is a classic cloud computing use case. If you can use this strategy, go for it. In that case, SLA enforcement probably isn’t quite as important. Similar results can be achieved with a private cloud setup as well.
The client I described earlier had another notable aspect to their story. They were using subscription keys for the mobile app. Unfortunately, the mobile app can be installed on a rooted phone, decompiled, and studied. Any data that is distributed with a mobile app (or Single Page App that is running in a browser) should be treated as public information. A subscription key is supposed to be private. Obviously, in this case, the subscription key is no longer private. Luckily, the API that the subscription key is supposed to be protecting is public and only works with public data. Had the data been part of a higher data classification, this would have been a serious problem. But, that’s not what we have here. The subscription key was removed from the application and placed in a configuration file that can be downloaded from a website.
Which brings us back around to the point of this post. How do you prevent third-party access to public APIs that you don’t want others calling, but do not want your own mobile app to require authenticating the end-user or provide any other burdensome login process? The answer as near as I can tell is that you do not and that if you truly do not want third-parties calling the public API, then you probably shouldn’t be considering it public and should require authentication of the end-user.
For completeness, another approach that was considered was having the app register the device it was installed on and then making sure that device was known when the public API was invoked. That approach would probably mostly yield the desired result, but just seems like overkill for the situation I described. Likewise, I do not believe this would prevent a third-party app installed on the same device from calling the API with all other factors being the same.
Any other ideas for how to deal with this situation? Leave a comment.