Organizations adopt continuous security at a rapid pace. It quickly becomes a topic of every day for mature DevOps team. Security topics include (cloud) infrastructure components and applications as well as the tech stacks which it all possible. Compared to a couple of years ago, managers don’t need to be convinced so much anymore. Since “everything is code” which translates to infrastructure and applications, it’s important to secure this code. Secure coding enhances application security for DevOps teams. What are good secure coding practices and which aspects require special attention? In this post, you will find out. Besides the technical challenges, we will also look at the business side of things.
Think from a broad perspective
Secure coding is not just about writing the actual secure source code by DevOps teams. Seen from an enterprise perspective, there is a lot more to explore.
Systems and applications need to adhere to predefined security requirements for the criticality of the data it needs to protect. Without security requirements, DevOps teams don’t know where to spend their security efforts on.
Business continuity is one of the main drivers for security requirements. Daily operations must go on every day in order to remain relevant in this fast-changing world. Without a major focus on this topic, nothing else matters.
Besides this, end-to-end security which spans the entire organization is important. Employees need to think from the perspective of the end-users to securely execute every task or process they are involved in. They need to be able to remediate any issue in case it might pose a problem to continue their service offerings to end-users.
Evaluation of what has been delivered through feedback loops is king, therefore, the security of a system can’t be evaluated if there are no security requirements.
Defense in depth
Layered security is a synonym for defense in depth. Your organizations’ security efforts should not focus on a single security aspect seen in isolation. Applications such as websites should be secured using multiple layers which all act as a specific “boundary” trying to stop an attacker before the critical application data is compromised.
With the example of an end-user’s website in mind, the following layers help to understand defense in depth:
- Don’t expose your (internal) website directly to the outside world. Use a firewall to keep unwanted traffic out. In the public cloud, points of entry vary across different resources. In addition to a (global) firewall, protect your data buckets or network security groups of your virtual network.
- Use an intrusion detection system for your networks. In the cloud, perform audit logging and detect (network & application) anomalies. Raise your eyebrows in case of anything that looks suspicious. Even better: create alarms to notify the application owner. In case of serious trouble, also notify any SOC or CISO department to help the DevOps team in charge.
- Besides a firewall and an intrusion detection mechanism, protect your Virtual Machines and containers. Run hardening scripts, anti-virus and malware protection to boost security aspects.
These three layers make it harder for an attacker to penetrate your systems. If one layer fails to block an attacker, the next one might prevent them from entering the underlying system.
The KISS method
Popular refactoring techniques advocate: clean up everything until there is nothing left to remove. The application features should still be what the end-user expects. Build systems that are easy to understand and easy to maintain. This ensures you can release faster and more frequently, troubleshoot faster and it also helps to understand the security implementations that give an answer to the security requirements. The KISS method (Keep It Simple, Stupid) aligns with that principle. The simpler the better since fewer mistakes can be made and more people can understand it. Be sure to follow security standards and don’t invent (or try to do so) your own.
Obfuscating data sent to external systems
Clean code is beautiful to read. Uncleared data should not be sent to external systems. Obfuscate your data to transform it from classified to unclassified data which is a bit more difficult to interpret. Common obfuscating techniques are encryption and data masking. Both have pros and cons, it’s good to keep in mind the different options.
Data validation and transportation
Professional developers always validate data that enters their systems. Without data validation, you can’t be sure your system behaves as expected. Anything which is not validated should not be trusted at all.
Access denied: the default behavior
Don’t trust anyone or anything which tries to access your infrastructure components or applications. Access denied should be your default behavior. Don’t use blacklists that cannot capture all potential use cases, use whitelists instead. Only allow access when certain criteria are met. Don’t create any (manual) exceptions, ignore management requests which tend to overrule this policy 🙂 It’s even better to show them what could happen if you don’t validate input and run a malicious query to reveal critical customer data. This helps to convince them why their input validation is not “nice to have”.
Least privilege principle
Default deny is the default behavior and when you actually give a person or system access to an application or its underlying data…do so with only a minimum set of privileges. Don’t use privileged accounts for regular access. Create dedicated system accounts with a limited set of permissions dedicated for the task it needs to execute. Temporary access tokens that grant access for only a specified amount of minutes help to control access even tighter. Tokens should invalidate as soon as access is not needed anymore.
All of these measures help to reduce the option which an attacker can use to gain access to (critical) aspects of your system.
Treat every input as potentially hostile, whether it be input from an actual human or from another system. Proper input validation techniques include the min and max length of the data and the characters which are allowed. All else should be blocked.
Some pitfalls to be aware of:
- Take special care when validating file uploads. Be sure to check for known extensions, set file-size limits, check the (extracted) paths of the file(s), etc.
- Learn how to properly craft regular expressions – some programming languages help you with this. For example the Apache Commons Validator.
Shift security left
Security efforts are most effective if they are embedded early on in the Software Development Life-cycle. It’s much cheaper to fix a vulnerability before a commit of your source code and run it through your CI/CD pipeline compared to fixing it in production.
Design and architect with security policies in mind
Security policies arise at different stages of the software application. Suppose you want to enforce certain cloud security policies in the AWS cloud, it is very helpful to design your application in such a way that it works in AWS. For example, you don’t need to expose your application to the internet, it’s wise to keep in mind that a security policy will check this later on. If this is the case, your application should restrict internet-based traffic from the very beginning.
Another example is the least privilege principle: if you have multiple components which require integration paths with other systems, it’s important to segregate the privileges of those systems upfront. Security policies that will be implemented later on will enforce this, later on, so you are prepared.
Listen to compiler warnings
Application source code that needs to be compiled in order to get a “ready to use software package” benefits from the highest warning level which is available to you. Detailed warnings in your Integrated Development Environment help to prevent bugs and (potential) vulnerabilities in an early stage.
Compiler warnings are an early indicator that something is wrong. Perhaps you recognize the phrase code smell? It’s important not to ignore these, since they can create problems later on in the Software Development Life-cycle.
Secure coding is not complete without threat modeling. This technique focuses on the collection and classification of potential threats to which an application might be subjective. Without threat modeling, you can’t properly design mitigation actions and you can’t focus on the most important issues compared to lower priority ones.
Quality Assurance techniques help to identify and eliminate vulnerabilities. Conduct fuzz testing, PEN testing, and security audits (by a third person) also enhance security. It’s important to have more than just the DevOps team look at the source code.
Viewing an application from a different angle helps to capture issues that might have been missed by the team who works on it on a daily basis. Besides this, it also helps to clear out assumptions and other misalignments.
Secure coding practices help to build applications that are more secure. Various techniques and methodologies fulfill the topic of “security” in the DevOps teams. In larger organizations, it is wise to establish and set up teams to design and advocate secure coding amongst all developer teams. I hope these tips helped you to improve the security of your applications while developing them faster than before.