A CISO Perspective on the Importance of Separating Authorization Policy and Application Code
Jan 26th, 2023
Strong separation of duties is crucial to support a modern and secure technology stack. In my mind, it’s no longer “best practice,” it’s a fundamental practice. Just as the group granting access shouldn’t be determining the rules for how to grant access, the application code governing how to interact with data shouldn’t determine who interacts with that data.
Let’s set the stage with how separating authorization policy and application code promotes the segmentation of duties. After that, I’ll dive into a brief discussion about authorization configuration as code and how that promotes auditability and other key security considerations that empower security teams to set policy in a way that enables application development and IAM agility.
Criticality of Separation of Duties
Separation of duties is a core information security principle that stands for separating access provisioning from access authorizations. That separation has implications for technology derived from physical counterparts.
In the physical world, separation of duties prevents events akin to arbitrary privilege escalation. Well-defined standards are self-enforcing. If the person or system granting access must follow defined standards, then deviation from those standards can be identified. That identification can be audited and alerted upon and access becomes a way to facilitate business.
My favorite simple example of that self-enforcement is a key or badging system. If there are rooms A, B, C, and D and I should only have access to A and B based on attributes or roles, then my access to C and D is a deviation from those standards. If I have a security group making those standards and an IAM group enforcing those standards, then separation of duties is preserved.
Preserving separation of duties facilitates straightforward and secure access controls:
- There is a baseline for what appropriate access should look like: handing out and revoking keys follows a defined and documented process. In that way, provisioning and deprovisioning follows the baseline.
- The security group can collect an audit trail for access provisioning. In a physical example, a ledger of who has copies of which keys.
- The security group is aware of all requests for deviation from those standards. If the person handing out the keys isn’t empowered to change to whom they give keys, they need to escalate requests for deviation from baseline.
- A precedent for not deviating from standards can be set and enforced by both teams.
- Checks and balances exist for multiple parties to validate deviation from standards.
- If policies change, they don’t impact the how of provisioning access, just the what of provisioning access. The security group can change to whom they provide keys. The IAM group can change how they provide keys.
As described, that system is simple, and efficient with delays only happening when deviations from baseline are requested. It’s self-enforcing because the ease of access provisioning and the delays resulting from “special” access requests disincentivize those requests unless absolutely necessary.
By collapsing standards enforcement and provisioning into one role, the benefits of consistent access are eroded and there’s a greater incentive to ask for “special” access. In my experience, that happens for one reason: if the person granting access can modify those standards on the fly, then there’s a potential excuse or justification every time access is granted.
Those excuses and justifications complicate identifying access legitimacy. Where there are excuses and justifications changing to whom access is granted, every deviation from baseline may be “legitimate.” Worse, the baseline can drift over time. So access that was previously legitimate may no longer be and vice versa.
If the baseline drifts the IAM group must be vigilant in rationalizing access across the board. Provisioning and deprovisioning keys become a guessing game of when someone joined. Straightforward tracking of who has access to what at any given time is also compromised.
Finally, when collapsing the functions of enforcement and provisioning documentation about each function is often also combined. That blurs the lines between what the process looks like and how the process should be enforced. Where compromises are made in one area, those can leak into the other and escalate baseline drift.
Why Policy as Code Supports Separation of Duties
What I described in the previous section was the physical analog of providing access without appropriate tracking, auditing, or monitoring. The complexity of information systems brings orders of magnitude more things to access, ways to access them, and user volume. In the technology world, authorization without separation of duties becomes very convoluted.
Hard-coding access controls into applications exacerbate that complexity. If someone meets the conditions for specific access, whether they should have that access or not, they will have that access. That can lead to unintended access provision or, even worse, nefarious access.
Furthermore, modern application architecture is rapidly shifting towards increased compartmentalization, containerization, and consequent agile service delivery. Hard coding permissions runs counter to those modern design choices by making access controls inflexible, unadaptable, and slow. The downstream impacts of standardization, monitoring, and alerting only serve to further bog down service delivery to business operations and customers.
In my opinion, modern application design demands compartmentalized access policy enforcement. I believe that compartmentalization incentivizes speed and service delivery. When access controls are separated from enforcement, they can be standardized and compartmentalized. With standardization comes the ability to automate. Automation results in expedited service delivery. Compartmentalization, then, facilitates the agile development and testing of authorization policies without significant impact on application operations.
Treating policy as code and extracting it from application logic averts the issues incumbent with hard-coded access policies. It supports agile modification of access and standardization. It also allows the policy to be used and reused in various assets where modification provides consistent and predictable results.
Separating the rules by which authorization is enforced and the mechanism through which authorization is granted facilitates the separation of duties. The security team can set policy and the application team can implement policy. Most access can be automated in a way that supports business and customer needs for quick and accurate service delivery.
What That Means to Me
As a CISO, I care about simple and effective security. Simple and effective security is successful security.
In my experience, compromising on either the simplicity or the efficacy of security makes that solution unsuccessful. If a solution is too complex, no one wants to implement and maintain it. Also, complexity often leads to potential gaps, and therefore vulnerabilities.
If a security solution is too ineffective it will also be unsuccessful. At some point, increasing compromises to the security solution will either 1) render it ineffective or 2) the “advantages” attributed to these compromises will no longer match the increase in risk from those compromises. When that point is reached the security solution does not meet the needs of the service being delivered.
Separating authorization policy from application code–and treating that authorization policy as code itself–is a simple and effective solution. Templatized and containerized authorization policies make it easy to validate, secure, and modify by security teams. It’s also easier for application teams to implement and can be used consistently across many assets.
By separating the duties for implementing and enforcing authorization through authorization policy as code, it’s also potentially easier to audit and secure authorization events. It’s certainly easier and more effective to harden one policy than many different hard-coded ones, all of which may have different logic and functions. Standardization of authorization calls also means easier telemetry aggregation and alerting for unexpected behavior.
At first blush, treating authorization policy as code appears to be one of the rare win-win scenarios for a security solution where security best practices promote and facilitate straightforward operations and save everyone time. As a CISO, I try to capitalize on those opportunities whenever I can. Not only do they bolster security, but they do so in a way that highlights how security helps. Security becomes perceived as a business driver and facilitator instead of a hindrance (worst-case) or insurance (best-case).
Open Policy Containers
There are a few ways to implement this compartmentalization and enforce the separation of duties. One of the more compelling mechanisms for doing so is a newly adopted CNCF sandbox project called Open Policy Containers (OPCR), which secures Open Policy Agent (OPA) policies across the lifecycle.
OPA is a graduated CNCF project that makes it possible to express authorization policy as code. But OPA policies are packaged up as tarballs, and implementing a secure software supply chain for OPA policies is largely an exercise left to the reader.
OPCR treats authorization policy as containerized code. It transforms OPA policies into immutable images that can be tagged, signed, tested, and versioned. I think it’s an interesting approach to address critical architectural and information security concerns that accomplish many key objectives:
- Allowing testing of access policies
- Separating the what and the how of access provisioning
- Creating a uniform baseline for authorization policy
- Containerizing and therefore compartmentalizing authorization policy
- Facilitating signing of authorization policy for peak auditability
In short, meeting those objectives makes for a secure authorization policy that’s easy to administer and incorporate into numerous organizational assets. Doing so also makes authorization management agile and facilitates business needs.
Containerizing authorization policies facilitate good security practices and address critical families of vulnerabilities, like OWASP A01:2021, Broken Access Control, and OWASP A03:2021, Injection. A hardened OPA connection denies access by default and limits how authorizations are called and granted. When combined with appropriate input validations and other privilege escalation limitations, OPCR can provide for significantly hardened authorization policies.
Bolstering that security is the ability to sign policy versions and push those consistently where incorporated. Version control is critical in policy management and that’s now easily available via a few commands. By implementing strong access policy version control, not only are authorization events auditable and actionable but so too are changes to authorization standards. Every change is exposed and easy to review and evaluate.
Those changes are also straightforward to test. Where code is compartmentalized and containerized, it can be incorporated into test environments and the effects of changes reviewed. That’s a far cry from hard-coded access controls.
Separating authorization policy and application code can be a difficult proposition when first embarking on the journey, especially with legacy applications. By treating authorization policy as code, however, information security and application teams are able to enforce strong separation of duties in multiple areas.
By doing so, those teams both benefit through improved efficiency, focus, and operational benefits. In that way, authorization policy as code can be a business and operational facilitator as well as provide quality security safeguards.