Policy Lifecycle Management from VS Code and CLI with Styra Link

8 min read

Many engineers like to stick to the IDE or the command line as they use those for their daily tasks instead of jumping into yet another SaaS web application. To improve the Styra DAS experience for them, we developed Styra Link, a tool that allows users to perform most of the tasks of the Styra DAS UI and manage OPA from the CLI or from VS Code. Styra DAS offers a fully integrated policy authoring and lifecycle management experience in a web-based UI.

Setup for GitOps-driven Kubernetes admission control

Styra DAS supports many policy-as-code use cases (e.g. Istio, Terraform, AWS API Gateway). For this article, I decided to go with a Kubernetes admission control scenario. I created two Kubernetes clusters (staging and prod) and I’ll be setting up a System for both using Styra Link. I will mostly be using the staging System until the promotion part in the last section.

Creating the prod and staging Systems

To get started we need to create Systems in Styra DAS for both our clusters and install OPA on them. Styra DAS already had a CLI tool simply called “Styra”. Styra Link is really a new extension to the existing tool adding new commands. Each cluster-system pair will need its own directory to store policies and Link configuration. Let’s create one for the staging cluster first.

$ mkdir staging && cd staging
$ styra link
Link connects your development workflow to the power of Styra DAS

  styra link [command]

Available Commands:

  bundle        Interact with the bundles for a Styra Link project, locally and remotely
  config        Read, set up, or modify different configurations for a Styra Link project
  global-config Read global configurations for Styra Link
  init          Initialize a new Styra Link project connecting to a new or existing Styra DAS system
  rules         Find and generate rules based on the available rule libraries for the project system type
  test          Run your unit tests using the latest authored policies
  validate      Validate your latest authored policies against your currently deployed system

When I initialize Styra Link it will connect my local directory to a System of my choice or create a new System.

$ styra link init
Welcome to Styra Link! Let's get you started...
? Do you want to create a new Styra DAS system, or connect with an existing one? New
? What is the name of your project? staging
? What kind of project is this? Kubernetes
? Where should policies be stored in your project? policies
? Do you want to set up git integration now or later? Now
? Git remote URL git@github.com:adam-sandor/link-k8s-policies.git
? SSH Private Key File Path /Users/adam/.ssh/id_ed25519
? SSH Key Passphrase (optional)
? How would you like to sync your policies? Branch
? Git branch (e.g. main) main

This will create a ./styra/config file in my local directory for other Styra Link commands to understand how and what System to connect to. It will also create a directory called “authorization” and download the Rego code from Styra DAS. 

It’s up to me at this point to initialize the current directory as a Git repository and push the code to the remote repo, so let’s do that. I’ll omit the command outputs here as some are long and not interesting.

$ git init
$ git remote add origin git@github.com:adam-sandor/link-k8s-policies.git
$ git pull origin main
$ git add -A
$ git commit -m "inital commit"
$ git push --set-upstream origin main

Finally, I have to install OPA on the staging cluster using installation instructions from the new System called staging. Styra Link doesn’t support doing this just yet, so I’ll have to navigate to the Web UI. The installation instructions are under the Settings section of the System.

Now I need to do the same process for the prod cluster. Create a new directory for that one and do all the steps from above. Once done I’ll go back to the staging directory to continue.

The Styra Link Workflow

From here on we can modify the policies by pushing changes to the Git repo. Styra DAS will detect the changes and deploy the policy bundle to the running OPA agents that are connected to the System. Other actions like testing and validation will be performed by Styra Link making API calls to Styra DAS. This workflow is illustrated below:

Styra Link workflow using Git and Styra DAS

A simple K8s admission control policy

Let’s add a policy that will deny the usage of the “latest” tag on any Pod in our Kubernetes cluster. We could just write some policy but Styra Link offers us the option to search the Styra policy library. I would like to deny any Pods to be started with an image with the “latest” tag to run on the cluster.

$ styra link rules search "latest"                                                                     
   KEY: block_latest_image_tag
   Prohibit container images that use the `:latest` tag.

Looks like block_latest_image_tag is the rule I want to use, so let’s get the actual code. The “-t enforce” flag will produce code for a rule that is enforced (as opposed to ignored or monitored).

$ styra link rules use -t enforce "block_latest_image_tag"                                               
enforce[decision] {
	decision := {
		"allowed": false,
		"message": message,

I just need to copy this into authorization/src/policy/com.styra.kubernetes.validating/rules/rules/validating.rego.

package policy["com.styra.kubernetes.validating"].rules.rules

enforced[decision] {
  decision := {
    "allowed": false,
    "message": message

Test & Validate

Before publishing my policies I need to make sure they work as intended. Styra Link gives me several tools for this:

  • Unit testing
  • Impact Analysis using Decision Log Replay
  • Compliance checks

To create a unit test I add the following code to authorization/src/policy/com.styra.kubernetes.validating/test/test/test.rego

package policy["com.styra.kubernetes.validating"].test.test

import data.policy["com.styra.kubernetes.validating"].rules.rules as rules
import data.global.test.assert as assert

test_dont_allow_latest_tag {
  result := rules.enforce with input as {
    "apiVersion": "admission.k8s.io/v1",
    "kind": "AdmissionReview",
    "request": {
      "kind": {
        "group": "",
        "kind": "Pod",
        "version": "v1"
      "object": {
        "apiVersion": "v1",
        "kind": "Pod",
        "metadata": {
          "labels": {
            "run": "nginx"
          "name": "nginx",
          "namespace": "default",
        "spec": {
          "containers": [
              "image": "nginx",
              "imagePullPolicy": "Always",
              "name": "nginx",

  list := [ item | result[item] ]

  assert.equals(1, count(list))
  assert.equals(false, list[0].allowed)

I can now run styra link test to run the unit tests:

$ styra link test

NAME                         PASS   DURATION    LOCATION                                                                                                  ERROR
test_dont_allow_latest_tag   true   950.901µs   systems/c0f4df77b27f4d58a40b52dc62d93898/policy/com.styra.kubernetes.validating/test/test/test.rego 6:1     -

Total 1: 1 pass, 0 failed, 0 errors

While at first sight this might seem the same as running opa test, there is an important distinction. Running unit tests with Styra Link will run them in the context of the System as configured in Styra DAS. That means the tests will have access to Libraries, Stacks and Datasources configured for the System. Notice the usage of the asset library in the example above. That code is pulled from Git by the Styra DAS backend so it’s not available locally. This is the same situation as when running unit tests from the Styra DAS UI.

Once I’m happy with my unit test, the next step is running a validation. First we run Impact Analysis using Decision Log Replay. This means replaying past decisions against the draft rules to see how many decisions have different outcomes. Those nginx Pods in the output below were created with the latest image tag, so applying my policy changes would cause these decisions to have opposite outcomes. The results below can be interpreted as: Out of 22 replayed decisions 16 have changed (or 72.73%). That means our policy change will have a significant impact on the cluster.

$ styra link validate decisions
Mar  9 14:30:27.834   9.774s     16 (72.73%)   22/4644     16        0/0      862/862

TIMESTAMP             ID                                     DECISION   TYPE         NAMESPACE   USERNAME                         OPERATION   KIND   NAME
Mar  9 14:29:55.431   b1825753-f7c1-4d5e-9514-9e9d982bcf92   DENIED     validating   default     kubernetes-admin                 CREATE      Pod    nginx4
Mar  9 14:29:52.985   227ce572-0198-4c2b-930a-224d73de6f35   DENIED     validating   default     kubernetes-admin                 CREATE      Pod    nginx2
Mar  9 14:29:56.717   ff9af285-cd37-453b-85c5-1b8415361d6b   DENIED     validating   default     kubernetes-admin                 CREATE      Pod    nginx5

We have one more validation to run, which is checking the cluster for compliance violations based on the new rules. This will verify if there are any existing workloads on the cluster that violate the policy. Styra DAS doesn’t evict any resources, but this list is useful for identifying which ones need to be handled in some way.

$ styra link validate compliance                                                         

Mar  9 14:29:50.000   default     nginx    Resource Pod/default/nginx should not use the 'latest' tag on container image nginx.
Mar  9 14:29:52.000   default     nginx2   Resource Pod/default/nginx2 should not use the 'latest' tag on container image nginx.
Mar  9 14:29:54.000   default     nginx3   Resource Pod/default/nginx3 should not use the 'latest' tag on container image nginx.
Mar  9 14:29:55.000   default     nginx4   Resource Pod/default/nginx4 should not use the 'latest' tag on container image nginx.
Mar  9 14:29:56.000   default     nginx5   Resource Pod/default/nginx5 should not use the 'latest' tag on container image nginx.

Total: 5

As I created these Pods a few minutes earlier, they show up both in the Decision Log Replay (which replays recent decisions only) and the Compliance View as the Pods are still running on the cluster.

Deploying policies

Now that I’m happy with the policy blocking all images with the latest tag, I’m ready to actually deploy it to the staging cluster, or more precisely the OPAs running on the cluster.

Styra Link together with DAS offers a number of options for deploying policy changes, depending on whether the System is configured to deploy a branch, tag or commit hash. 

I configured the System to track the main branch, so once I merge my changes to the branch, the deployment happens automatically as soon as Styra DAS notices the change in the repo. I prefer this streamlined way of deployment for staging a more controlled way using tags for prod.

To deploy to staging, I’ll push my changes to main:

$ git add -A
$ git commit -m “deny images with latest tag”
$ git push

It takes a minute or two until Styra DAS notices that the branch is updated and the OPAs notice that Styra DAS has built a new bundle. Once that’s done I’ll run a quick test on the cluster:

$ kubectl run --image=nginx nginx-denied

If all goes well this will produce the following error message:

Error from server: admission webhook "validating-webhook.openpolicyagent.org" denied the request: Enforced: Resource Pod/default/nginx-denied should not use the 'latest' tag on container image nginx.

Now that I can see that my policy is working on staging I can promote to production by tagging the commit in Git with a version number:

$ git tag 1.0.0
$ git push --tags

Now is the time to make use of our production Styra Link setup in the prod directory. Let go over there from staging. In the prod directory we can configure production to use the 1.0.0 tag we just pushed to Git:

$ styra link config git -d -t 1.0.0

I can now run the same test against production:

$ kubectl ctx kind-prod
$ kubectl run --image=nginx nginx-denied

VisualStudio Code

CLI tools are great, but for those of us who like to work from an IDE, a dedicated extension is even better. Styra Link is available as part of the Styra VSCode extension with all the functionality exposed as actions.

The extension also installs the Styra CLI if it’s not installed yet and checks to keep it up to date. It will also guide the user through connecting the Link to the tenant.

Another cool feature of the extension is that it helps with writing Rego by providing code snippets. Some of these help to access language-level features while others offer a more convenient way to insert rules from the Styra DAS compliance libraries.

Inserting Rego language snippets (start typing “rego”):

Inserting Kubernetes built-in rules without using the Search action:


Styra Link provides the perfect integration of Styra DAS with to mange OPA with the CLI and VS Code. This allows engineers to stick to the tooling they use day to day for managing their policy. In a follow-up post, I will show how this also allows for a smoother CI/CD pipeline integration.


Try Enterprise OPA

In 5 minutes you can upgrade your OPA to one purpose-built for enterprise needs.

Speak with an Engineer

Request time with our team to talk about how you can modernize your access management.