Getting Started with KiCS part 2: Queries

In part one of this getting started series we looked at a simple KiCS install and scan, and added viewing the results in a web page.

Next it’s, time to take a look at queries. Queries are the rules that we use to find the misconfigurations in the IaC files that we’re scanning. Rules are written in Rego - more of which later.

KiCS ships with over 1200 existing queries, which is going to catch a lot of problems. But, given as it’s relatively new, there are certain scenarios that it might not catch. Fortunately developing your own queries is straightforward.

First we need some IaC with a ‘bad’ configuration that KiCS isn’t catching - or at least isn’t catching all the errors.

Looking back at the Kubernetes YAML from part 1 it’s a bit unrealistic. Usually pods are deployed as part of a service or a deployment. So let’s take the container definition and wrap it into a deployment object.

apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2
kind: Deployment
metadata:
  name: test-deployment
spec:
  selector:
    matchLabels:
      app: testApp
  replicas: 1 # tells deployment to run 2 pods matching the template
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: sec-ctx-4
        image:  gcr.io/google-samples/node-hello:1.0
        securityContext:
          privileged: true
          capabilities:
            add: ["NET_ADMIN", "SYS_TIME"]

This is the same configuration as before, but wrapped in a few more layers.

Let’s run KiCS against it:

KiCS Example>./kics scan -p ./priv2.yaml -o /var/www/html/results.html --report-formats "html"

Most of the results are there, but where has my ‘Don’t run a privileged container’ warning gone? - it looks like the existing query doesn’t catch the privileged: true tag if it’s nested. So we had better fix that with a new query.

Looking at the documentation - the first step is to run the KiCS parser against our file to turn it into the universal JSON format that KiCS uses. The documentation is written assuming you have a built form source KiCS install, but we’re using binaries, so the commands look just a little different.

We run the KiCS command without a command modifier, and with the -p and -d :

KiCS Example>./kics -p priv2.yaml -d input.json

This produces the normal KiCS output to the console, but the output file is the JSON that the KiCS parser will convert our file into.

Now we can examine the JSON

KiCS Example>cat input.json
{
        "document": [
                {
                        "kind": "Deployment",
                        "metadata": {
                                "name": "test-deployment"
                        },
                        "spec": {
                                "selector": {
                                        "matchLabels": {
                                                "app": "testApp"
                                        }
                                },
                                "replicas": 1,
                                "template": {
                                        "metadata": {
                                                "labels": {
                                                        "app": "nginx"
                                                }
                                        },
                                        "spec": {
                                                "containers": [
                                                        {
                                                                "name": "sec-ctx-4",
                                                                "image": "gcr.io/google-samples/node-hello:1.0",
                                                                "securityContext": {
                                                                        "privileged": true,
                                                                        "capabilities": {
                                                                                "add": [
                                                                                        "NET_ADMIN",
                                                                                        "SYS_TIME"
                                                                                ]
                                                                        }
                                                                }
                                                        }
                                                ]
                                        }
                                }
                        },
                        "id": "8519892b-cff2-479a-bb29-2eb3a206b73f",
                        "file": "/home/robert_haynes/kics/priv2.yaml",
                        "apiVersion": "apps/v1"
                }
        ]
}

Now we need to craft some rego to pick up the misconfiguration. Let’s find the query that picks up the original mis-configuration

KiCS Example>ls assets/queries/k8s | grep privileged
container_is_privileged
psp_set_to_privileged
KiCS Example>
>ls assets/queries/k8s/container_is_privileged/
metadata.json  query.rego  test
KiCS Example>

It looks like the query.rego is what we should look at:

package Cx

CxPolicy[result] {
        document := input.document[i]
        spec := document.spec
        types := {"initContainers", "containers"}
        containers := spec[types[x]]

        containers[c].securityContext.privileged == true

        metadata := document.metadata

        result := {
                "documentId": document.id,
                "searchKey": sprintf("metadata.name={{%s}}.spec.%s.name={{%s}}.securityContext.privileged", [metadata.name, types[x], containers[c].name]),
                "issueType": "IncorrectValue",
                "keyExpectedValue": sprintf("spec.%s.name={{%s}}.securityContext.privileged is false", [types[x], containers[c].name]),
                "keyActualValue": sprintf("spec.%s.name={{%s}}.securityContext.privileged is true", [types[x], containers[c].name]),
        }
}

If you’re not familiar with the rego language at all, now might be a great time to go and read this tutorial.

We can see the rule build an array (for want of a better term) of objects by defining and appending values, document, spec, then selecting some parts based on a some variables we define

types := {"initContainers", "containers"}
containers := spec[types[x]]

the spec[types[x]] selects for all the JSON objects contained by the “initContainers” or “containers” label under the “spec” label. the [x] notation denotes a list, essentially

Similarly the containers[c].securityContext.privileged == true matches of any of the container objects with securityContext.privileged == true

You can experiment with this in the rego playground

All well and good, but why didn’t this rule catch our errant container?

Taking a look at the output we generated above you can see that there is a nested spec construct

"spec": {
	<blah>
	"template": {
		<blah>
		"spec": {
                  "containers": [

So our rule above won’t find any containers labels under the first spec, as they are under the second.

Let’s test this theory in the rego playground:

rego playground 1

From the result we can see that no policy match has occurred. How do we fix this?

Since the container object is another level deep, we might just need to add an additional spec in there:

spec := document.spec

spec := document.spec[f].spec (the ‘f’ is an arbitrary list object, in rego you can define it using the some f statement, but KiCS doesn’t seem to mind)

Let’s give that a try:

rego playground2

Now we can see that the rule has matched and we’re getting output messages.

Great, so we’re all done? Not quite there are a few housekeeping tasks to do. First we should generate a new UUID for our query:

KiCS Example>./kics generate-id
03aad349-82fd-44c2-aecd-7e2fc4009f89
KiCS Example>

Now we need to set up the directory for our new query:

cp -R ./assets/queries/k8s/container_is_privileged/ ./assets/queries/k8s/container_is_privileged_recursive

ls ./assets/queries/k8s/container_is_privileged_recursive
metadata.json  query.rego  test

Let’s edit the files to have our new config:

metadata.json - change the id field to the UUID generated above, and change the query name (you will need to match this in the “positive_expected_result” file below) any other fields for the response message.

query.rego - make the change for the spec to line spec := document.spec[f].spec

There is also the test directory containing:

negative.yaml

positive.yaml

positive_expected_result.json

For the positive.yaml we can use the source yaml we started with above, for the negative.yaml we can use the same file but with privileged: false in the security context and then we can edit the positive_expected_result.json to identify the line (19) and query name (whatever you changed it to in the metadata.json file)

Let’s check the results of running a new scan:

newresult

And we are done! We have created a new query for our IaC scanning - and if you create one that’s useful you might even want to contribute it back to the community.

2 Likes