Much of securing an API is the same as securing any other service, concerning application security, system security, and network security. The differences are in the expectations of use from far and wide. Service-oriented and microservice architectures have taken over the world of web development. This move to API-driven development has evolved and modernized our software, making it capable of scale and maintenance. This separation of purpose has driven better security, easier automation, and contracts-based programming.
This is what APIs really are, contracts between subscribers and services. Each service is a contract that tells a user with assurance that, given a set of inputs, they can expect a solid set of outputs. When it comes to the security of those contracts, security can and should be built into the initial design. The biggest risks to your API are things like:
- Bots and attackers looking for automated ways into your system.
- Unpatched vulnerabilities in your application’s libraries, framework, or the underlying system and packages.
- Ignoring best-practices in development and deployment.
- Exposing yourself to unnecessary risk by failing to reduce attack vectors.
Use a language and framework capable of change. Change is inevitable, and evolvable software is critical to security (not to mention lifetime costs to your business). Sometimes change is small, requiring a patch to a single library, while other times change is disruptive, requiring updates to your entire framework. In either case, having a plan to upgrade libraries and frameworks is critical, which often includes a significant testing phase. A dual infrastructure for development is beneficial here, which, in an environment like AWS, is painless. Frameworks like Ruby on Rails make this particularly easy, with a Gem file and a lock file that controls libraries and versions.
Make Yours a Philosophy of Security
In all things, follow the eight principles of security. These have been salient since first being published in 2000, but are even more effective when creating an API-driven design! All of these are important to build into your service endpoints, but I would highlight:
- Complete mediation: ensure correct access to every object, each time.
- Fail-safe defaults: access should be denied by default.
- Separation of privilege: access should be isolated to purpose; this is often accomplished through role-based permission models, but there are other options depending on your context.
- Least privilege: along with separating privilege, you should grant users only the privileges they need to accomplish the duty of the service.
Trust No One, Not Even Yourself
This isn’t my first time writing this phrase. When creating a service endpoint, assume bad actors will be attempting to exploit your service. Sanitize your data, fuzz your endpoints, cover corner-cases in unit tests. Don’t be afraid to try to break your own service, embrace the concept. Test, test, and test again. When designing interactions between your service and others ensure that you’ve shielded the app (and the consumer) from failure conditions; your application should degrade gracefully when problems occur.
Use Web-Based Standards
Standards like CORS and hosts settings allow you to control who’s allowed to talk to your services. This is done within the application and in the web server directives. If you’re running API endpoints, it’s likely that you want to open up access to everyone, but the host settings will eliminate bots who only have your IP address from attempting to access to your service endpoints.
Unlike at application layer, where I would not expect (or recommend) automated updates for packages and libraries, the system packages and the underlying operating system should be updated for security automatically, often. Rarely these do have negative impacts on performance or break interactions within your system entirely. That being said, the good far outweighs the bad. In a world where a single vulnerability can leave millions of servers suddenly exposed, you need to reduce exposure time by as much as possible. In systems that are based on RHEL, this is reasonably easy to do with the yum-update-security package and then running yum updates with the --security flag.
Continue Applying Your Philosophy of Security
Once again, using the principles of security, consider who needs access to the system itself and in what capacities. Here this is applied in a different context. Limit access to ports using your firewall (iptables) or use security groups (in AWS). Ensure users have the correct permissions: not everyone needs sudoers, and even this can be limited to particular directories and binaries. The application itself (and accompanying software) should run as a non-privileged user.
Harden Your Systems
System hardening has not changed too much in the last 15 years. Intrusion detection systems have become more fully featured and automated, with applications like Suricata and Moloch providing a combination of many disparate features. Reduce or eliminate root access through SSH, use keys to login instead of passwords, and ensure bad actors are automatically banned using tools like Denyhosts or Fail2Ban. Enable SELinux, or at least make it permissive (warning, this can screw with other stuff on your system). Don’t just lock down ports using your firewall, refuse information to potential attackers by dropping their packets on the floor. Use tools like ModSecurity and associated rules to block bad actors from accessing your services, perhaps due to frequency or types of requests.
Log everything, and analyze those logs regularly to identify patterns, and then abnormalities. Create a monitoring dashboard and look for odd traffic patterns, alert engineers when there are spikes in traffic (good or bad), and so on.
Lock Down Your Network Topology
Starting with your border, ensure that outsiders are left on the outside of your network. AWS makes this easy using VPCs (Virtual Private Clouds). Ensure that these outsiders can only talk to your services within a public subnet, which interacts with your private subnet services. For a breakdown of how this should look, check out an article I wrote earlier about AWS network security.
Lockdown Inter-Service Communication
Your service might communicate with other services and/or databases. As in the point above, the database should probably reside in an isolated subsection of your network. Beyond that, you should reduce the risk of lateral attack movement by ensuring that even within your network only those services that communicate with one another are given access.
Use Vendors and Tools
Tools like WAFs (Web Application Firewalls), such as those offered by Akamai or AWS can protect you from bad actors by detecting bots, rerouting DDoS attacks, and blocking attackers.
You can avoid many of the hardships of securing your environment by being mindful in design. The biggest risks to your API can be mitigated by:
- Planning for changes in your environment and application, automating as much as possible.
- Using tools and best-practices to lock down your application, your system, and your network.
- Monitoring your application, system, and network to understand normal and abnormal patterns.
- Testing, testing, and testing again.