kat is a lightweight, local testing tool for Kubernetes Admission Policies (ValidatingAdmissionPolicy and MutatingAdmissionPolicy). It allows you to write test cases using standard Kubernetes manifests and verify your policies' behavior without needing a running cluster.
- Standard Kubernetes YAML: Write tests using plain K8s manifests - no new DSL to learn.
- Full CEL Support: Uses the official Kubernetes CEL libraries for 100% accurate evaluation.
- Comprehensive Policy Support:
ValidatingAdmissionPolicy(Allow, Deny, Warn, Audit)MutatingAdmissionPolicy(Mutate, No-op)
- All Operations: Supports CREATE, UPDATE, DELETE, and CONNECT operations.
- Golden File Testing: Automatically verifies mutated objects against expected golden files.
- Rich Context: Simulate complex scenarios with
userInfo,namespaceObject, andmatchConditions. - Parameter Testing: Test parameter-driven policies (
paramKind/paramRef).
go install github.com/zemanlx/kat@latestOr build from source:
git clone https://github.com/zemanlx/kat.git
cd kat
go installThe recommended way to use kat is to run it from the root of your repository. It will automatically discover and execute all tests found in tests/ directories recursively.
kat .This commands will:
- Find all
testsdirectories. - Locate the corresponding Policy and Binding for each test (by looking up in the directory tree).
- Execute all found tests.
You can also target specific directories or files:
# Run tests for a specific policy
kat ./policies/my-policy/tests
# Run a specific test case
kat ./policies/my-policy/tests/my-policy.basic-test.object.yaml-run <regex>: Run only tests matching the regex pattern.-v: Verbose output (shows detailed execution steps).-json: Output results in JSON format.
kat -v -run "prod-.*-deny" .kat is designed to fit naturally into existing Kubernetes repositories, including those using Kustomize.
The tool works by discovery:
- It looks for
tests/directories containing test files. - It looks for policy and binding files in the parent directory of
tests/.
Supported filenames include:
policy.yaml/policies.yamlbinding.yaml/bindings.yaml- Any file ending in
.policy.yamlor.binding.yaml
Note: You can define multiple policies and bindings in a single file (separated by ---), or split them across multiple files. The tool loads all valid policy/binding resources found in the directory.
This allows you to keep your tests co-located with your policy definitions. You just need to add a tests/ folder alongside your manifests.
Example Layout:
policies/
├── team-label-policy/
│ ├── kustomization.yaml # (Optional) Kustomize file
│ ├── policy.yaml # The AdmissionPolicy definition
│ ├── binding.yaml # The AdmissionPolicyBinding
│ └── tests/ # Add this folder for kat
│ ├── team-label.has-label.allow.object.yaml
│ ├── team-label.missing.deny.object.yaml
│ └── ...
In this setup, running kat . at the root will automatically find the tests directory, associate it with the policy in the parent team-label-policy directory, and execute the tests.
Tests are defined by file naming conventions. The filename structure determines the test type and expectations.
Pattern: <policy-name>.<test-name>.<expect>.<type>.yaml
Requirement: The <policy-name> prefix must match the metadata.name of the policy being tested.
(If a directory contains only a single policy, kats automatically associates all tests with that policy).
- expect:
allow,deny,warn,audit(for Validating) - type:
object,oldObject,request,params
1. Expect Allow:
Create a file ending in .allow.object.yaml.
# my-policy.test-1.allow.object.yaml
apiVersion: v1
kind: Pod
metadata:
name: allowed-pod
labels:
cost-center: "123"2. Expect Deny:
Create a file ending in .deny.object.yaml.
# my-policy.test-2.deny.object.yaml
apiVersion: v1
kind: Pod
metadata:
name: denied-pod
# Missing required labels3. Expect Specific Message:
Add a .message.txt file side-by-side.
# my-policy.test-2.deny.message.txt
Pod must have a cost-center label
1. Mutation Test: Provide the input object and the expected output (golden file).
- Input:
my-policy.test-1.object.yaml - Expected:
my-policy.test-1.gold.yaml
If the actual mutation result differs from the golden file, the test fails and prints a diff.
Use a .request.yaml file to provide additional admission context like user info or namespace details.
# my-policy.test-1.allow.request.yaml
operation: CREATE
userInfo:
username: "system:serviceaccount:kube-system:job-controller"
namespaceObject:
metadata:
labels:
environment: productionFor policies using paramKind, provide the parameter resource.
# my-policy.test-1.allow.params.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: policy-config
data:
excludedNamespaces: "kube-system,monitoring"You can mock Kubernetes Authorizer responses (SubjectAccessReview) for policies that use authorizer checks in CEL.
Create a .authorizer.yaml file side-by-side with your test files.
# my-policy.test-1.allow.authorizer.yaml
- group: ""
resource: "pods"
subresource: ""
namespace: "default"
verb: "create"
decision: "allow"The mock matches requests based on group, resource, subresource, namespace, and verb. By default, any check not explicitly mocked will return "NoOpinion" (which usually results in a denial or failed check depending on policy logic).
- UPDATE: Provide both
.object.yaml(new) and.oldObject.yaml(old). - DELETE: Provide only
.oldObject.yaml(resource being deleted).
Check the test-policies-pass directory for a comprehensive set of examples covering:
- Basic validation and mutation
- Parameters and ConfigMaps
- Namespace-based logic
CONNECToperations (kubectl exec)- Match conditions
- Warnings and Audit annotations