During a recent infrastructure audit for a Mumbai-based fintech firm, I observed that despite having a 200-page security manual, their AWS environment had 14 publicly accessible S3 buckets and three RDS instances with encryption disabled. Manual compliance checks failed because the rate of resource deployment via Terraform exceeded the security team's ability to review pull requests. This is a systemic failure in traditional governance. Hardening cloud infrastructure now requires moving away from "PDF-based policy" to Policy as Code (PaC), often supplemented by real-time threat detection to catch drift.
What is Policy as Code?
Policy as Code is the practice of managing and enforcing rules, regulations, and best practices through machine-readable definition files. Instead of a security analyst manually checking if a security group allows port 22 from 0.0.0.0/0, we write a script that evaluates the configuration automatically. For organizations moving toward zero-trust, using a browser based SSH client can eliminate the need for these risky open ports entirely. We treat these policies exactly like application code, complete with version control, unit testing, and automated deployment.
In our testing, we found that using Open Policy Agent (OPA) allows us to decouple policy logic from the underlying service logic. Whether you are validating a Terraform plan, a Kubernetes manifest, or an Envoy proxy configuration, the policy engine remains the same. This unified approach reduces the cognitive load on security engineers who otherwise have to learn different proprietary policy languages for every cloud provider.
The Evolution from Manual Compliance to Automated Governance
In the legacy model, compliance was a "point-in-time" event, often occurring quarterly or annually. This is insufficient for cloud-native environments where infrastructure changes occur hundreds of times per day. I have seen organizations spend weeks preparing for a CERT-In audit, only to have their security posture drift within hours of the auditors leaving.
Automated governance shifts this paradigm to "continuous compliance." By integrating policy checks directly into the developer workflow, we prevent non-compliant resources from ever being provisioned. This is the difference between finding a fire and preventing the match from being struck.
Why Policy as Code is Essential for Modern DevSecOps
DevOps emphasizes speed and agility, which often creates friction with traditional security gates. PaC resolves this by providing immediate feedback to developers. When a developer attempts to commit code that violates a policy—such as failing to assign a "Cost-Center" tag required by the DPDP Act 2023 for data tracking—the CI/CD pipeline fails instantly with a descriptive error message.
This automation is particularly critical in the Indian context, where the Digital Personal Data Protection (DPDP) Act 2023 mandates strict control over personal data. We use PaC to ensure that any resource handling PII is automatically restricted to the ap-south-1 (Mumbai) or ap-south-2 (Hyderabad) regions, preventing accidental data residency violations that could lead to penalties of up to ₹250 crore.
The Core Benefits of Policy as Code Implementation
Scaling Security and Compliance at DevOps Speed
Scaling security manually is a losing battle. As your cloud footprint grows from 10 nodes to 10,000, your security team cannot grow proportionally. PaC allows a small team of security researchers to define guardrails that govern thousands of developers. We observed that implementing OPA reduced the time spent on manual architecture reviews by 70% in a mid-sized enterprise environment.
Reducing Human Error through Automation
Human reviewers are prone to fatigue and oversight. A reviewer might miss a single "allow" rule in a sea of 500 lines of JSON. A policy engine like OPA will never miss it. By codifying the "deny" logic, we ensure that every single deployment is scrutinized with the same level of rigor, regardless of the time of day or the complexity of the change.
Improving Auditability and Traceability
When policies are code, they live in Git. This provides a perfect audit trail. We can see exactly who changed a policy, why they changed it (via commit messages), and who approved the change (via PR reviews). For compliance with RBI’s "Master Direction on IT Governance," this level of traceability is invaluable. Auditors can simply review the Rego files and the Git history rather than hunting through disparate logs and screenshots.
Step-by-Step Guide to Policy as Code Implementation
Step 1: Defining Your Policy Requirements and Standards
Before writing a single line of Rego, you must define what "secure" looks like. I recommend starting with the CIS Benchmarks and the OWASP Top 10 for your specific cloud provider. However, you must also layer in organization-specific rules. For example, an Indian NBFC might require that all database backups be stored in a separate AWS account within the same geographic region to meet data sovereignty requirements.
Common Baseline Requirements
- No public access to S3 buckets or RDS instances.
- Mandatory encryption at rest using Customer Managed Keys (CMK).
- Strict tagging requirements (Owner, Environment, Project).
- Disabling the use of the "Root" user for any automated task.
- Restricting ingress traffic to known corporate CIDR blocks.
Step 2: Choosing the Right Policy Engine and Language
While there are several options, Open Policy Agent (OPA) has emerged as the industry standard. It uses a declarative language called Rego. Unlike imperative languages, Rego allows you to describe the desired state, and the engine determines how to evaluate it.
Why OPA/Rego?
- Cloud Agnostic: Works with AWS, Azure, GCP, and Kubernetes.
- Extensible: Integrates with CI/CD, Admission Controllers, and Service Meshes.
- Performance: Designed for high-performance evaluation at the edge.
Step 3: Writing and Versioning Your Policies
We treat Rego files like any other source code. This means they reside in a dedicated repository, often structured by the service they govern. For example, a policies/terraform/aws directory would house all rules related to AWS infrastructure.
Example logic for checking AWS instance regions
package aws.compliance
default allow = false
Ensure all instances are in Mumbai region
allow { input.resource_type == "aws_instance" input.attributes.availability_zone == "ap-south-1" }
Deny if encryption is not enabled for EBS volumes
deny[msg] { input.resource_type == "aws_ebs_volume" input.attributes.encrypted == false msg := sprintf("EBS Volume %v must be encrypted", [input.id]) }
Step 4: Integrating Policies into the CI/CD Pipeline
The most effective way to enforce policies is at the "Pre-Commit" or "Pre-Deployment" stage. We use conftest, a utility built on top of OPA, to test configuration files.
Testing a Terraform plan output against policies
terraform plan -out=tfplan.binary terraform show -json tfplan.binary > tfplan.json conftest test tfplan.json --policy ./policy/ --output json
If the conftest command returns a non-zero exit code, the pipeline fails, and the infrastructure is not provisioned. This prevents the "Security Debt" that occurs when we try to fix misconfigurations after they are live.
Step 5: Continuous Monitoring and Enforcement
Policy enforcement doesn't stop at the pipeline. We must also guard against manual changes made via the cloud console—often referred to as "ClickOps." For Kubernetes, we use OPA Gatekeeper. Gatekeeper acts as an Admission Controller, intercepting requests to the Kubernetes API server and validating them against our Rego policies before they are committed to etcd.
Checking for existing ConstraintTemplates in the cluster
kubectl get constrainttemplates
Practical Policy as Code Examples
Infrastructure as Code (IaC) Scanning Examples
When scanning Terraform, we look for specific resource attributes that indicate a security weakness. A common issue is the use of default VPCs or public IP assignments. To mitigate these risks, teams should follow a comprehensive SSH security hardening best practices guide to secure their remote access points.
package terraform.analysis
Deny public IP assignment to EC2 instances
deny[msg] { resource := input.resource_changes[_] resource.type == "aws_instance" resource.change.after.associate_public_ip_address == true msg := "Public IP assignment is forbidden. Use a Bastion host or VPN." }
Kubernetes Admission Control Policies
Kubernetes is notoriously difficult to secure due to its permissive defaults. We use OPA to enforce security contexts. This is a direct mitigation for CVE-2021-25741, where subPath volume mounts could be exploited to gain host-level file system access.
package kubernetes.admission
Deny containers that run as root
deny[msg] { input.request.kind.kind == "Pod" container := input.request.object.spec.containers[_] not container.securityContext.runAsNonRoot == true msg := sprintf("Container '%v' must set runAsNonRoot to true", [container.name]) }
Cloud Resource Tagging and Compliance Examples
Under the DPDP Act 2023, data classification is mandatory. We enforce this by requiring a Data-Classification tag on all storage resources.
package aws.tagging
required_tags = {"Owner", "Project", "Data-Classification"}
deny[msg] { resource := input.resource_changes[_] resource.type == "aws_s3_bucket" provided_tags := {tag | resource.change.after.tags[tag]} missing := required_tags - provided_tags count(missing) > 0 msg := sprintf("S3 Bucket is missing required tags: %v", [missing]) }
Network Security Group Authorization Rules
We frequently see security groups with "Any-Any" rules. This Rego snippet identifies and blocks any rule that allows traffic from the entire internet on sensitive ports like 3389 (RDP) or 22 (SSH).
package aws.network
deny[msg] { input.resource_type == "aws_security_group" ingress := input.attributes.ingress[_] ingress.from_port == 22 ingress.cidr_blocks[_] == "0.0.0.0/0" msg := "Inbound SSH (Port 22) from 0.0.0.0/0 is strictly prohibited." }
Top Tools for Implementing Policy as Code
Open Policy Agent (OPA) and Rego
OPA is the foundation of the PaC ecosystem. It is a general-purpose policy engine. I recommend using the OPA CLI for local development and testing.
Evaluate a local policy against an input file
opa eval --data policy.rego --input input.json "data.main.deny" --format pretty
HashiCorp Sentinel
Sentinel is a proprietary policy-as-code framework integrated into HashiCorp's enterprise products (Terraform Cloud, Vault, Nomad). While powerful, it lacks the broad ecosystem support of OPA. It is best suited for organizations already heavily invested in the HashiCorp stack.
Pulumi CrossGuard
Pulumi takes a different approach by allowing you to write policies in general-purpose languages like TypeScript, Python, or Go. This is useful if your security team is more comfortable with standard programming languages than with Rego's logic-based syntax.
Checkov and Terrascan for IaC
These are static analysis tools specifically for IaC. While they come with hundreds of pre-built policies, they can also be extended. I often use Checkov in the local developer environment (pre-commit) and OPA in the CI/CD pipeline (pre-deployment) for a multi-layered defense.
Best Practices for a Successful Implementation
Treating Policy Code with the Same Rigor as Application Code
Policies must be tested. OPA provides a built-in test runner. We write unit tests for every Rego rule to ensure that "Allow" rules don't inadvertently allow malicious traffic and "Deny" rules don't block legitimate traffic. For teams looking to master these skills, specialized training and career development programs can help bridge the gap between development and security operations.
Running unit tests for Rego policies with coverage report
opa test . -v --threshold 100
Implementing 'Dry Run' or Advisory Modes First
When introducing PaC to an existing environment, a "Hard Deny" approach will break everything and alienate developers. I recommend an "Advisory" phase where violations are logged but not blocked. This allows you to tune the policies and help teams remediate existing issues without stopping production.
Collaborating Between Security, Ops, and Development Teams
Security should not write policies in a vacuum. If a policy blocks a specific instance type that the Ops team needs for performance reasons, it creates friction. We use a "Policy Guild" approach where representatives from each team review proposed rules before they are enforced.
Ensuring Policy Portability Across Environments
Avoid hardcoding environment-specific values (like Account IDs or VPC IDs) into your Rego files. Use OPA's "Data" feature to inject context-specific information at runtime. This allows the same policy file to be used across Dev, Staging, and Production.
The Future of Automated Governance
Rego Logic Injection and Security Pitfalls
As security researchers, we must also look at the vulnerabilities within the policy engine itself. CVE-2022-31030 demonstrated that even OPA-Envoy can have vulnerabilities where malformed HTTP requests bypass policy checks. Furthermore, we must be wary of "Rego Logic Injection." This occurs when developers exploit a "Default Allow" stance. If a Rego policy is written to deny specific conditions but defaults to allow, an attacker can simply omit the fields the policy is looking for to bypass the check.
I always recommend a "Default Deny" posture:
package global.governance
Start with a closed gate
default allow = false
Explicitly define what is allowed
allow { is_authorized_user is_compliant_resource }
Next Steps for Your Organization
The transition to Policy as Code is not just a technical shift but a cultural one. Start by identifying your top three most frequent cloud misconfigurations. Codify them in Rego, integrate them into a single pipeline, and observe the results. As you gain confidence, expand to Kubernetes admission control and runtime authorization.
To verify your OPA installation and check the version for known vulnerabilities, run:
opa version
Ensure you are running at least version 0.43.0 to avoid early Rego parsing bugs that allowed certain bypasses in complex nested object evaluations.
