Deploying an application in production involves many compliance and security considerations. The organization may also set internal standards for the software development teams. For example, the global QA group in a large organization may enforce certain policies to ensure quality, consistent services.
Policies play an important role in maintaining the sanity of all environments where large-scale deployments happen, but enforcing these policies in complex software deployments is tedious and time-consuming. This is where policy as code (PaC) comes in.
In this post, we will explore policy as code, outline its benefits, show how it works, and demonstrate how to implement it. We will also cover popular policy management tool: Open Policy Agent (OPA).
To demonstrate the role policies play in ensuring security, compliance, and operational standards in large-scale deployments, here are some examples:
- Security
- Password complexity requirements
- Data encryption standards for data at rest and in transit
- Network access control lists
- Compliance
- Access logging and auditing requirements
- Geographical data storage restrictions
- Data retention and deletion schedules
- Infrastructure
- Allowed instance types for VMs
- Tagging policies for cloud resources
- Backup and DR configs
- Network segmentation
- Cost control
- Cost thresholds and alerts
- Automatic shutdown of resources
- Usage of reserved instances
- Development
- Code review requirements
- Direct commits on main branches
- Vulnerability scanning
- Naming conventions
These are just a few examples of policies you may know. Enforcing them cannot happen from a single point of control. Addressing each policy above may require independent setups or additional development and configuration within the application code to enforce these constraints.
However, with policy as code, the automation and management of these policies become easy.
Policy as code (PaC) is a method of defining and enforcing policies through code, stored and managed in a centralized policy engine. A policy is a set of rules or guidelines governing decisions and actions within a system. The policy engine delivers decisions in real time when system actions are performed, acting as a checkpoint before any request is processed.
PaC solutions are typically designed to interpret various types of policies, allowing teams to configure policies based on specific conditions. Once conditions are met, the PaC engine delivers the decision via an API, decoupling decision-making from other system components and reducing distributed implementation overhead by centralizing and decoupling policy logic from other system components.
Policy as code use case examples
Here are three practical examples of how policy as code can be applied across different environments.
Access control in cloud environments
In cloud environments, PaC can enforce access control policies to regulate which users or applications have access to specific resources.
For example, when a user attempts to access sensitive data, the PaC engine checks if the user has the required permissions, preventing unauthorized access and ensuring compliance with data security standards.
Compliance checks in CI/CD pipelines
PaC helps maintain compliance in Continuous Integration and Continuous Deployment (CI/CD) pipelines by enforcing operational, security, and compliance policies. Before code or infrastructure changes are deployed, the PaC engine can ensure the changes adhere to regulatory requirements, such as GDPR or HIPAA, by validating configuration settings and checking for security vulnerabilities.
Operational consistency in multi-cloud environments
In multi-cloud setups, PaC solutions can ensure consistency by enforcing uniform policies across different cloud providers.
Policies might standardize tagging conventions, network security configurations, and other settings, helping organizations avoid configuration drift and maintain governance across diverse environments.
Policy as code vs. compliance as code
Policy as code (PaC) uses code to set and enforce rules for system operations. Compliance as code (CaC) is a part of PaC focused specifically on meeting regulatory and security standards by embedding compliance standards and requirements directly in the code, ensuring the system follows legal and industry guidelines.
Here are some benefits that Policy as code brings to your environments:
- Efficiency – PaC allows automated policy enforcement at any scale, which makes it more efficient than manual enforcement.
- Consistency – Defining policies as code in a universal format ensures they are consistently enforced across multiple systems and environments. This helps prevent policy violations and reduce breaches.
- Speed – The encoded nature of PaC results in faster implementation and makes the operations fairly quick.
- Governance and compliance – PaC enables real-time monitoring and automated reporting, which ensures adherence to regulatory requirements. Standardizing policies across the organization improves the implementation of governance and compliance.
- Visibility – Because of the structured nature of PaC, it is possible to render them on UI so that stakeholders can take and understand the posture.
- Collaboration – It provides a uniform and central way to manage policies. Thus teams can readily understand each other’s policy enforcement, eliminating the time wasted in back-and-forth communication.
- Version control – PaC readily takes advantage of version control mechanisms to keep track of all policy changes and revert to a specific version if necessary.
- Reduced configuration drift – Automation pipelines responsible for promoting changes to higher environments during infrastructure deployments remain on track by referring to the PaC engine. PaC helps improve consistency in this way, reducing the possibility of configuration drift. (Learn more about drift: What is Configuration Drift? Tools, Causes & Risks)
Policy as code uses code to define, manage, and enforce policies within a system. Here’s how PaC and policies operate at a technical level:
- Policies are written as code, typically in declarative languages like JSON, YAML, or domain-specific languages (DSLs), which describe the conditions and rules the system must enforce. These policies can include access controls, security settings, resource limits, or compliance requirements.
- A PaC system includes a policy engine, such as Open Policy Agent (OPA), which interprets and evaluates policies. The engine is embedded in the system, often as an API service, which other system components can query when making decisions.
- When a system component needs to make a decision, it consults the policy engine by sending a query that includes context information (e.g., user identity, resource being accessed). The policy engine processes this information against the defined policies and returns a decision (e.g., allow, deny, modify).
- The policy engine centralizes policy logic, allowing consistent rules across services and applications without embedding policies in each one. This decoupling allows for easy updates, as any policy changes are applied universally through the policy engine without needing to modify each service individually.
- PaC solutions enable continuous validation, automatically checking that actions and configurations comply with the defined policies. This is especially useful in CI/CD pipelines or infrastructure-as-code (IaC) setups, where policies are evaluated before deployment, reducing the risk of non-compliance and security issues.
Implementing PaC in your environment involves several key steps. The steps below summarize a generic process, but you may want to consider customizing it based on your needs.
Step 1: Gather the data and see which policies need to be defined
You should begin by gathering data about the current implementation and defining the correct approach. You can either leverage various infrastructure scanning techniques to scan through the inventory for configurations on each system or do it manually.
This step deals with identifying and analyzing the infrastructure resources, security settings, access controls, environmental configurations, etc.
It is also important to know how applications are deployed, their dependencies on other components, and how they integrate with third-party systems, databases, etc. All of this information is crucial from a policy standpoint. It helps us identify gaps and plan for policy implementation using PaC.
In the data collection phase, we typically understand which policies need to be defined and whether we need to maintain any data to support policy decisions. This information is mostly already available and dispersed across teams within the organizational structure. However, this phase provides an opportunity to further the efficacy of policies.
Step 2: Choose the appropriate PaC tool
Depending on the policy information gathered, research the PaC tools that cover most of your policy implementations. Ideally, you would want to select a tool that helps manage all the policies in a single repository.
The PaC solutions come in various shapes and sizes. Some of them are tool-specific or eco-system-specific, and some are capable of providing generic solutions across the landscape. For example:
- checkov is a PaC tool that checks for misconfigurations using IaC/configuration-based technologies like Terraform, CloudFormation, Kubernetes, etc.
- Hashicorp’s Sentinel is a PaC tool that enforces policies on Hashicorp products.
- OPA is a generic implementation of PaC, and any system can potentially integrate with it.
See how 1Password is democratizing access to infrastructure by using Spacelift’s policy engine with OPA’s Rego — read the full story.
However, in real-world implementations, you may have to choose a combination of tools for complete coverage. Consider the future policy enforcements for their compatibility with the PaC tool of your choice.
Step 3: Write the policies as code
Once the tool is chosen, start converting (writing) all the selected policies for that tool in the language that it supports. We will see an example of this in the next section, where we implement PaC for a specific infrastructure policy using an Open Policy Agent (OPA).
Depending on your IT landscape, you may have multiple ways to integrate PaC into your systems. However, they are usually evaluated during the CI/CD automation where a feedback loop already exists. Here are some examples:
- The code/config can be evaluated before committing it to the repository. You can leverage various CLI tools and IDE plugins or create a custom API call to readily check your code against the central policy engine.
- These checks can be implemented when the code is being built in the pipeline.
- For applications already deployed, these checks are implemented via monitoring systems, where they can raise appropriate alerts if they find an anomaly as per defined policies.
This shows that once the policies are encoded, there are multiple ways to integrate the decision-making in the systems. This is useful because now the developers and admins can make appropriate changes to their code, adhering to the policies while the policies themselves are being managed centrally.
Step 4: Integration testing
Before enforcing the policies via PaC across the environment, test them on a subset of components in a lower/controlled environment. Make sure the PaC tools API works well and delivers expected decisions for various scenarios.
The PaC API is usually consumed across the development workflow at various stages. It is important to ensure all those integrations are properly completed for policy enforcement.
Step 5: Deploy and monitor
Once the tests are successful, roll out the policy enforcement to higher environments. Set up logging to be informed about the decisions being made by the PaC tool. Gather feedback from development and operations teams. This helps further fine-tune the policies where required.
Step 6: Governance
Finally, create documentation and a workflow to update these policies for future changes.
For example, when new vulnerabilities are identified, PaC should be updated to accommodate checks for the new vulnerabilities. Policy enforcement is a broad topic and relates to multiple functions in an organization. Setting up a governance process establishes a standardized process for faster policy updates.
OPA implements policies as code using the Rego language. It is a domain-agnostic tool capable of implementing policies for any format of incoming data. OPA can be used as a library or embedded into the service itself.
The figure above provides a high-level summary of how it works, and here is a quick summary of all the components:
- Policy – The policies are defined using Rego in a separate repository.
- Data – A JSON database that maintains accompanying data required by policies for decision-making.
- Input query – OPA accepts query data in the form of JSON for validation.
- Service – OPA provides a service to accept incoming requests and return the decision made based on policy and data.
- Decision – After evaluating the incoming request against the policies, OPA returns the decision as a JSON value.
This mechanism decouples the decision-making logic from the actual application. It’s flexible enough to work with any format and return decision values in the desired format, making it capable of implementing policies for a vast array of applications.
Let us assume that you have a Terraform script to provision an EC2 instance in AWS. For auditing purposes, you want to include an ‘Env’ tag for each resource created on all the AWS accounts.
To implement this auditing policy, you can create a policy.rego
file in the OPA repository and add the following code.
package ec2
deny[msg] {
resource := input.resource_changes[_]
resource.type == "aws_instance"
not resource.change.after.tags.Env
msg := sprintf("EC2 instance %v is missing required 'Env' tag", [resource.address])
}
The code above is self explanatory, but you can find more information about the rego syntax here.
It accepts the JSON input and checks for an Env tag value to be present. If not, it throws an error message. The JSON input here is nothing but the Terraform plan output in JSON format.
This makes it easy to implement OPA in the CI/CD automation. Before provisioning (terraform apply
command), you can simply create a plan output file in JSON format and provide it as input to the OPA CLI using the command below.
opa eval --data policy.rego --input plan.json "data.ec2.deny"
If the Env tag is not present, the OPA CLI generates a JSON output, as shown below. The value array will not be empty and will carry the error message defined in the policy.
{
"result": [
{
"expressions": [
{
"value": [
"EC2 instance aws_instance.example is missing required 'Env' tag"
],
"text": "data.ec2.deny",
"location": {
"row": 1,
"col": 1
}
}
]
}
]
}
If the Env tag is present, OPA will generate an empty value array as shown below, indicating no errors.
{
"result": [
{
"expressions": [
{
"value": [],
"text": "data.ec2.deny",
"location": {
"row": 1,
"col": 1
}
}
]
}
]
}
Using this JSON response, you can decide in the CI/CD pipeline whether to proceed with the provisioning. The same functionality can be extended to all the resources in the Terraform repo, and potentially, OPA’s flexibility can be extended to other application code as well.
Here are the best known PaC tools:
- Open Policy Agent: An open-source, general-purpose policy engine that allows you to define and enforce policies as code across diverse systems and applications.
- Prisma Cloud: It is focused on cloud security, where you can define and enforce security policies as code for the cloud resources.
- OWASP SecurityRat: OWASP is a well-known open-source cyber security foundation focused on improving the security of software applications. It is proactive in nature. Depending on the type of software artifact you want to build, it will provide a list of compliances you should adhere to. It is not exactly a PaC tool, but it helps strengthen security.
- Sentinel: This tool by Hashicorp is a PaC framework that can be integrated with HashiCorp products.
- Checkov: Static code analysis tool for IaC. It supports multiple IaC solutions.
- AWS Config rules: AWS specific service to configure automated security checks on AWS resources.
OPA is integrated into the Spacelift platform. You can define policies for different purposes:
- Use login policies to control who can log in to your Spacelift account and what role they should have once inside.
- Use approval policies to control who can approve or reject runs of your stacks, or to allow or reject runs automatically based on some other condition.
- Use plan policies to control what changes can be applied to your infrastructure. We defined this type of policy in HCP Terraform with Sentinel.
You can find the schema for the input for each type of policy in the Spacelift documentation.
Case study example
As a fintech with strict compliance and security requirements, Airtime Rewards appreciates how Spacelift policies enables it to leverage the benefits of CI/CD — but with guardrails. Spacelift utilizes OPA to support a range of policy types, enabling Airtime Rewards to codify its rules and decision-making to execute them in an automated way.
If you use Terraform to configure your Spacelift environment, you can create policies using the spacelift_policy resource type:
resource "spacelift_policy" "business_hours" {
name = "deny-run-outside-business-hours"
description = "Deny runs to start outside of business hours"
body = file("${path.module}/policies/business-hours.rego")
type = "APPROVAL"
space_id = "root"
labels = [
"autoattach:team"
]
}
This policy is configured as an approval-type policy. The content is provided in the body argument and kept in a separate file.
To enforce your OPA policies at scale in Spacelift, you can place them in the root space and add the special autoattach:<label> label, where <label> is a label you attach to the stacks where the policy should automatically be applied. In the example above, all stacks with the team label will automatically have this policy attached. This makes managing policies at scale a breeze.
To see an overview of all the policies you have defined, go to the policy overview page in Spacelift:
This list shows all policies, their type, their location, and any labels attached to them.
The various kinds of policies available in Spacelift are applied at different times. When a new run is triggered for a given stack, any approval-type policies are applied immediately. In the image below, a run was triggered outside business hours, and an approval policy automatically denied the run from continuing:
If the run passes all the approval policies, the plan phase starts. Once the plan phase is complete, any plan phase policies are evaluated, taking the plan output into account. This is similar to what we did using the GitHub Actions workflow described above.
The ability to apply policies in different phases is extremely powerful. You can stop a plan from happening if approval policies are denied, and you can stop an apply phase from going ahead if a plan phase policy is violated.
Note that when we created policies and applied them in Spacelift, we only ever explicitly provided one of the three required parts to run OPA: the policy itself. The Spacelift platform handles the other two parts — the input (the Terraform plan) and the query. To write successful policies, we must be aware of the schema of the input and the query Spacelift is using. All of this information can be found in the documentation for each type of policy.
If you want to learn more about Spacelift, create a free account today, or book a demo with one of our engineers.
PaC can drastically improve the management of policies—whether they relate to compliance, security, operational standards, or something else. A central management solution dedicated to implementing the logic and serving the decisions via API takes the cognitive load off of the development teams. It becomes easier to enforce PaC across all the teams, resulting in secure infrastructure and consistent service.
Solve your infrastructure challenges
Spacelift is a flexible orchestration solution for IaC development. It delivers enhanced collaboration, automation, and controls to simplify and accelerate the provisioning of cloud-based infrastructures.