If storing Infrastructure as a Code, such as Kubernetes manifests, linting your configuration files with Conftest is a great first step. If you've structured your IaaC repository based on your infrastructure layout, ie. Stage-Environment-Domain-Application, you could also use Conftest to help validate the service names or namespaces based on their path with the help of the --combine flag.

conftest test --combine --all-namespaces --data policy/data/dev dev 

This way we can define a policy which ensures our manifests are in the correct stage by requiring the namespace to be prefixed with the stage and placing the manifest in folder structure which has the stage as the root folder name.

deny_namespace_incorrect_path[msg] {
    manifest = input[idx].contents
    file = input[idx].path
    stagePath := split(file, "/")[0]
    namespace := manifest.metadata.namespace
    not startswith(namespace, stagePath)
    msg := sprintf("Namespace %s must start with stagePath: %s - FILE: %s", [ namespace, stagePath, file])
}

Given this Deployment:

---

kind: Deployment
apiVersion: apps/v1
metadata:
  name: conftest-example-namespace-fails
  namespace: app
  labels:
    app: conftest-example-namespace-fails
spec:
  replicas: 1
  selector:
    matchLabels:
      app: conftest-example-namespace-fails
  template:
    metadata:
      labels:
        app: conftest-example-namespace-fails
    spec:
      containers:
        - name: nginx
          image: nginx
          imagePullPolicy: Always
      restartPolicy: Always

Here is the output you would expect if the file were placed at dev/app/fails-namespace-conftest-example.yaml:

...
FAIL - Combined - k8s.namespace - Namespace app must start with stagePath: dev - FILE: dev/app/fails-namespace-conftest-example.yaml
...

Another benefit is that you can use show the relative filepath for the FAIL'ed policies by appending - FILE: %s and passing the file variable to the msg := sprintf(..):

FAIL - Combined - k8s.kind - Kind StatefulSet is not one of the allowed kinds: ["Deployment", "Service"] - FILE: dev/app/fails-kind-another-conftest-example.yaml
FAIL - Combined - k8s.kind - Kind ThisKindIsNotAllowed is not one of the allowed kinds: ["Deployment", "Service"] - FILE: dev/app/fails-kind-conftest-example.yaml
FAIL - Combined - k8s.namespace - Namespace app must be prefixed with stage prefix: dev- - FILE: dev/app/fails-namespace-conftest-example.yaml
FAIL - Combined - k8s.namespace - Namespace app must start with stagePath: dev - FILE: dev/app/fails-namespace-conftest-example.yaml

5 tests, 1 passed, 0 warnings, 4 failures, 0 exceptions

OPA Policies and Conftest are great tools for quickly defining linting rules which integrate easily into any pipeline. Up to this point I haven't found any tool which allows me to define such a rule without requiring me to use a specific convention.

Resources