VaultGRC EngineeringPolicy as code: which security controls to encode first
GRC Engineering · Guide

Policy as code: which security controls to encode first

Policy as code: which security controls to encode first

Harry WestFounder & CEO21 October 202611 min read
Inside This Article
What is policy as code?How does policy as code differ from infrastructure as code?How does policy as code differ from compliance as code?Which policy as code tools are there?Which security controls should you encode first?Should a policy start in enforce mode or warn mode?How do you handle exceptions and waivers?How do policy decision logs become audit evidence?Who should govern the policies themselves?What are the common mistakes with policy as code?Frequently asked questions
Book a discovery call

Key takeaways

Policy as code is writing security and compliance rules as version-controlled code that a policy engine, such as Open Policy Agent, Kyverno or Sentinel, evaluates automatically before changes reach production. GRCMANA recommends encoding six rules first: public exposure, network rules, privileged access, encryption, configuration baselines and change approval, each rolled out in warn mode before enforcement.

Decision logs
Open Policy Agent decision log events include the policy queried, the input to the query and bundle metadata, which the OPA documentation says enables auditing of policy decisions.
Sentinel enforcement levels
HashiCorp Sentinel has three enforcement levels: advisory (the default, allowed to fail with a warning), soft mandatory (must pass unless overridden) and hard mandatory (must pass).
Gatekeeper enforcement actions
OPA Gatekeeper denies admission requests that violate a constraint by default; its dryrun and warn enforcement actions let teams test a constraint without enforcing it.
Kyverno policy exceptions
A Kyverno PolicyException is a namespaced resource that lets named resources past a given policy and rule, for one-time deviations whose risks are known and acceptable.

Your compliance lead has forwarded two documents: the ISO 27001 surveillance audit plan and an enterprise customer's security questionnaire. Both ask how you stop a public storage bucket or an unreviewed change reaching production. Today the honest answer is code review and good intentions.

Policy as code turns that answer into a guardrail. This guide is for platform, DevOps and security engineering leads at UK B2B tech firms running Terraform and Kubernetes in the cloud. It sets out which rules to encode first, the ISO 27001 Annex A controls and SOC 2 criteria each one supports, how to roll rules out without breaking delivery, and how the engine's records become audit evidence.

What is policy as code?

Policy as code is writing security and compliance rules as version-controlled code that a policy engine evaluates automatically. The engine blocks or flags non-compliant changes in pipelines, provisioning or Kubernetes admission control before they reach production. Open Policy Agent, Kyverno and Sentinel are common engines. The value comes from encoding first the few rules that prevent the most audit findings.

HashiCorp's Sentinel documentation describes the idea well: once policies live in text files, you can apply version control, automated testing and automated deployment to them. A rule set by clicking in a console has none of that.

The Open Policy Agent documentation adds the design choice that makes it work. The engine makes the decision; something else acts on it. Your pipeline or cluster sends the change to the engine as data, asks a question, and acts on the answer.

Policy as code usually runs at three points:

  • The CI/CD pipeline. Rules check a Terraform plan or a Kubernetes manifest when a pull request is raised, so the developer sees the failure before merge.
  • Infrastructure provisioning. Rules check the plan again at apply time, where the change is about to become real.
  • Kubernetes admission control. Rules check every object the cluster is asked to create or change, including ones nobody put through the pipeline.

For Kubernetes, the admission point matters most. Of the three, it is the only one that catches a change made outside the pipeline.

How does policy as code differ from infrastructure as code?

Infrastructure as code describes what you want to build: the networks, clusters and storage, written as Terraform or similar. Policy as code describes what you will not allow to be built, whatever the infrastructure code says. One is the blueprint; the other is the building regulation that every blueprint is checked against before work starts.

The two sit side by side. Infrastructure as code gives the policy engine something to read, because a Terraform plan is structured data. Without it, policy as code can still check Kubernetes objects and cloud API calls. It just loses the earliest point of control.

How does policy as code differ from compliance as code?

Policy as code is preventive: it stops a non-compliant change before it lands. Compliance as code is detective: it checks what is already running against your controls and gathers the evidence an auditor samples. You need both, but they answer different questions and usually have different owners, the platform team for the first and the GRC lead for the second.

This guide stays on the preventive side. Detective checks, continuous control monitoring and auditor evidence packs belong to compliance as code, which GRCMANA covers in its own guide.

Which policy as code tools are there?

The common policy as code tools are Open Policy Agent, Kyverno, HashiCorp Sentinel and the policy services built into each cloud platform. They differ in policy language, where they enforce and licence. Many teams run more than one, for example Kyverno in the cluster and a cloud-native service at the account level. The table compares them neutrally; none is ranked.

EnginePolicy languageWhere it enforcesLicence and governance
Open Policy Agent (OPA)RegoGeneral purpose: Kubernetes (through the Gatekeeper project), CI/CD pipelines, Terraform plans, microservices and API gatewaysApache 2.0; CNCF graduated project since January 2021
KyvernoYAML and CEL (Common Expression Language)Kubernetes admission controller, command-line scanner and runtime scans; can also check other JSON outside KubernetesApache 2.0; CNCF graduated project since March 2026
HashiCorp SentinelSentinel languageInside HashiCorp products, such as policy checks on Terraform plans in HCP TerraformHashiCorp product, not open source
Cloud-native policy servicesJSON definitions or constraintsThe cloud account or organisation: Azure Policy, AWS service control policies, Google Cloud Organization PolicyPart of the cloud platform

Two details from the vendors' own documentation are worth knowing. HCP Terraform accepts OPA policies as well as Sentinel, so a Terraform shop is not locked to one language. And AWS service control policies never grant rights. They set the most that IAM users and roles in your organisation can do.

The engine matters less than the rules you put in it.

Which security controls should you encode first?

Encode first the rules that stop the most serious mistakes and are easy to check with few false alarms: public exposure, network rules, privileged access, encryption, configuration baselines and change approval. Each supports controls an ISO 27001 auditor or SOC 2 examiner will test. The table maps them by control ID; the sections below explain each one.

PriorityRule to encodeBlocksISO 27001 Annex ASOC 2 criteria
1Public exposurePublic storage buckets, databases and admin endpoints5.15, 8.20CC6.1
2Network rulesInbound access from anywhere to management and database ports8.20CC6.1
3Privileged accessWildcard admin roles, cluster-admin bindings, privileged containers8.2CC6.1
4EncryptionUnencrypted storage, volumes and plain-text listeners8.24CC6.1
5Configuration baselinesUnapproved images, missing owner tags, settings outside the baseline8.9CC7.1
6Change approvalChanges applied outside the pipeline or without review8.32CC8.1

We recommend this order because the first rules are yes-or-no, and a miss does harm at once. A bucket is public or it is not. A baseline rule needs more judgement, so it comes later, once the team trusts the engine.

1. Public exposure

This rule refuses any storage, database or admin interface that anyone on the internet could reach, unless someone has signed off the choice. It supports access control (5.15) and network security (8.20) in Annex A and CC6.1 in SOC 2. Enforce it at the Terraform plan and again at the cloud account level.

An illustrative rule, in words rather than code: deny any storage bucket whose access setting is public, unless an approved exception names that bucket.

2. Network rules

This rule refuses firewall or security group entries that open admin or database ports to any address. It supports network security (8.20) and CC6.1. Enforce it at the plan, and use a cloud-native service as a second line for changes made in the console.

3. Privileged access

This rule refuses roles with wildcard permissions, cluster-wide admin bindings for ordinary workloads, and containers that request privileged mode. It supports privileged access rights (8.2) and CC6.1. Enforce it in admission control for Kubernetes, and set an account-level ceiling with service control policies or their equivalent.

4. Encryption

This rule refuses unencrypted storage, unencrypted volumes and listeners that accept plain-text traffic. It supports use of cryptography (8.24) and CC6.1. It is easy to write, because in most resources the setting is one field.

5. Configuration baselines

This rule checks resources against an approved baseline: allowed image registries, required owner and data-classification tags, and hardened settings. It supports configuration management (Annex A 8.9) and CC7.1. The CIS Benchmarks give a consensus-based starting point, so you do not invent the baseline from scratch.

6. Change approval

This rule refuses changes that did not come through the reviewed pipeline, such as an apply run from a laptop or a deployment with no linked pull request. It supports change management (Annex A 8.32) and CC8.1. It comes last because it only works once the pipeline is the normal route for every change.

Should a policy start in enforce mode or warn mode?

Start every new rule in warn or audit mode, then move it to enforce once existing violations are fixed or excepted. Most of the engines above support this. Gatekeeper denies by default but offers dry-run and warn actions; Kyverno has Audit and Enforce; Sentinel has advisory, soft-mandatory and hard-mandatory levels; Azure Policy has audit and deny effects.

The engines' own descriptions make the trade-off clear. Gatekeeper's documentation says its warn action gives the same benefits as dry run and also tells the user why the change would have been denied. Kyverno's documentation describes policy reports as a way to see a policy's impact without causing disruption.

A rollout that does not break delivery follows five steps:

  1. Write and test the rule against sample inputs, including known-good and known-bad cases.
  2. Run it in audit mode against the existing estate, and list what it would have blocked.
  3. Fix or except each existing violation, with an owner for each one.
  4. Switch to warn mode in the pipeline, so developers see the message before it can stop them.
  5. Enforce on a published date, and tell the teams it affects before that date arrives.

Sentinel's soft-mandatory level is a useful middle step. The documentation says the policy must pass unless someone overrides it, and that the override provides non-repudiation, because someone has to override the failed policy explicitly.

A rule that only ever warns is a log entry, not a guardrail.

How do you handle exceptions and waivers?

Handle exceptions as records, not workarounds. Each exception names the rule, the resource, the reason, an owner and an expiry date. It goes through the same review as a code change. Kyverno builds this in with its PolicyException resource; in other engines, an exceptions file in the policy repository does the same job.

Kyverno's documentation frames the need plainly: some teams must allow a change that would violate a valid rule on a one-time basis, where the risks are known and acceptable. That is a risk acceptance, and it deserves the same treatment as any other.

We recommend four properties for every exception:

  • An owner who can explain the exception to an auditor.
  • A reason that links to the risk register entry or the ticket that approved it.
  • An expiry date, after which the rule applies again automatically.
  • A narrow scope, naming the resource rather than switching the rule off for a whole account.

An exception without an expiry date is a second, unwritten policy.

How do policy decision logs become audit evidence?

Policy decision logs become audit evidence because they record, for each change, which rule was evaluated, what was submitted and what the engine decided. Kept alongside the policy repository's history and the exception register, they show an auditor that a control exists, that it ran, and what happened when it failed.

OPA's decision logs are described in exactly these terms. Each event includes the policy that was queried, the input to the query and the bundle metadata, with other information that enables auditing of policy decisions. Kyverno writes its results to policy reports, which are Kubernetes resources, and Gatekeeper records audit violations on each constraint.

Together, these give you four kinds of evidence:

  • The rule itself, with its version history, reviewer and approval date, showing design.
  • Decision logs, showing the rule ran on real changes and what it blocked.
  • The exception register, showing deviations were approved, owned and time-limited.
  • Audit-mode reports, showing how existing resources stand against each rule.

For an ISO 27001 auditor, that is evidence for change management and configuration management. For a SOC 2 examiner, it supports CC8.1 and the access criteria. The SOC 2 trust services criteria page explains how those criteria are organised; the SOC 2 hub covers the report itself.

We recommend keeping decision logs for at least the length of your audit period. An ISO 27001 surveillance audit or a SOC 2 Type 2 examination looks back over months, not days.

Who should govern the policies themselves?

The policies need the same governance as any other control. Each rule has a named owner, sits in version control, passes automated tests before release, and is reviewed on a fixed cycle. Changes go through pull requests, so the rule set has its own change record. Nobody can quietly weaken a guardrail.

In practice, that means:

  • Ownership. One owner per rule, recorded in the repository, usually in the platform or security team.
  • Testing. Unit tests for each rule with passing and failing inputs; OPA and Kyverno both ship testing tools.
  • Versioning. Tagged releases of the rule set, so you can say which version was in force on a given date.
  • Review. A scheduled review, which we recommend at least annually, of whether each rule still matches the written policy and the risk it was meant to cover.

The written security policy still sets the intent. The code enforces part of it, and the review keeps the two in step.

What are the common mistakes with policy as code?

The common mistakes with policy as code are writing hundreds of rules on day one, offering no exception path, leaving rules without owners, and running everything in audit mode forever. Each one turns the engine into noise that developers learn to ignore. Noise is a poor thing to show an auditor.

  • Hundreds of rules on day one. Import a full rule library and you get a wall of findings nobody can sort. Start with the six above.
  • No exception path. If the only way past a rule is to disable it, people will disable it.
  • Rules nobody owns. Nobody reviews a rule without an owner, so it drifts from the risk it was written for.
  • Audit mode forever. Reports pile up and nothing is blocked. The control exists on paper only.

The fix for all four is the same. Encode fewer rules, own each one, and set a date for every rule to start enforcing.

Frequently asked questions

No. Open Policy Agent is one engine, and policy as code is the practice. Kyverno, Sentinel and the cloud-native services are policy as code engines too. The practice is defined by rules in version control that a machine evaluates, not by any one language.

No. Terraform plan checks in the pipeline and cloud-native services such as Azure Policy or AWS service control policies work without a cluster. Kubernetes adds admission control as a further enforcement point, which matters most when people can change the cluster directly.

No. ISO 27001 still expects documented policies that management approves. Policy as code enforces the technical part of them in a way you can test and evidence. Keep a reference from each rule to the policy statement it enforces, so the two can be reviewed together.

It depends on how many violations audit mode finds in what you already run. Each one needs a fix or an exception before you enforce. We recommend planning by rule, not by date: a rule moves to enforce only when its audit report is clean or fully excepted.

Profile image for Harry West, Founder and CEO of GRCMANA
Author

Harry West

Harry is the founder of GRCMANA, he helps regulated service providers close the enterprise trust gap — turning GRC, cyber resilience and AI governance from paperwork exercises into shippable systems.

Domain engine

GRC Engineering reference directory.

Every guide in this domain, grouped by level — fundamentals, intermediate and advanced. Open any entry for its core practices, implementation steps and the evidence it produces.

01Fundamentals2 guides
How to choose GRC tools: a vendor-neutral selection guide→Compliance automation: what to automate first, and what never to→
02Intermediate6 guides
GRC metrics: the KPIs that show your compliance programme is working, and how to collect them automatically→Security metrics: the 10 measures a B2B tech firm should report to its board, customers and investors→Automated evidence collection: build an audit evidence catalogue that collects itself→Security questionnaires: how to answer once and reuse everywhere→Trust centre: what to publish and how to run one→Control testing: design vs operating effectiveness, and how to automate it→
03Advanced4 guides
Unified control library: how to build one common control framework for every standard→Compliance as code: turning controls into automated checks auditors accept→Policy as code: which security controls to encode first→Continuous control monitoring: how to test controls automatically between audits→
GRC Engineering Accelerator

Ready to stop rebuilding evidence for every audit?

Work with a Trust Architect to automate evidence collection and continuous control monitoring, so your next audit and your next security questionnaire draw on the same live controls.

Book a call →
GRCMANA
  • About
  • Insights
  • Contact
Ask the Trust ArchitectBook a Call →
  • About
  • Insights
  • The Vault
    Compliance VaultCyber Essentials→ISO 27001→SOC 2→ISO 42001→
    Domain VaultGRC Fundamentals→Risk Management→AI Governance→GRC Engineering→
    Other ResourcesFree Tools→Templates→Newsletter→
  • Contact
Book a Call →Ask the Trust ArchitectFixed fee. ISO 27001 in as few as six weeks.
GRCMANA

Helping high-growth B2B tech startups close the Enterprise Trust Gap through security, governance, and AI resilience.

ISO 27001ISO 42001ISO 9001Cyber EssentialsEU AI Act ReadyNIST AI RMF

The Vault

GRC EngineeringAI GovernanceISO 27001ISO 42001All Vault Resources

Resources

BlogFree ToolsTemplatesNewsletter

Company

About GRCMANAContactPrivacy PolicyTerms & ConditionsCookie Policy
© 2025 GRCMANA · All rights reservedRegistered in England & Wales under company registration 16810805. VAT Registration 506308024