Policy Lifecycle Management from VS Code and CLI with Styra Link
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
Usage:
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
[/ruby]
? 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"
CONTAINERS: PROHIBIT `:LATEST` IMAGE TAG
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] {
data.library.v1.kubernetes.admission.workload.v1.block_latest_image_tag[message]
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] {
data.library.v1.kubernetes.admission.workload.v1.block_latest_image_tag[message]
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
systems/c0f4df77b27f4d58a40b52dc62d93898/policy/com.styra.kubernetes.validating/test/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
STARTED DURATION CHANGES DECISIONS SAMPLES ERRORS BATCHES
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
systems/c0f4df77b27f4d58a40b52dc62d93898/policy/com.styra.kubernetes.validating/monitor
=======================================================================================
TIMESTAMP NAMESPACE NAME MESSAGE
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:
Conclusion
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.