Repository: plexsystems/konstraint
Pull request #80 (2020-11-03 18:43:36)

Add parameters to lib.core
This adds a `parameters` rule to lib.core which allows the parameters to be accessed whether they're sent from Gatekeeper or through Conftest's `--data` flag. In order to not break any existing policies, the regex was updated to match on either `core.parameters` or `input.parameters`.


Patch:
diff --git a/.github/workflows/push_container.yaml b/.github/workflows/push_container.yaml
new file mode 100644
index 0000000..c38fb10
--- /dev/null
+++ b/.github/workflows/push_container.yaml
@@ -0,0 +1,42 @@
+name: Push Tagged Container
+on:
+  push:
+    branches:
+    - main
+    tags:
+    - v*.*.*
+
+jobs:
+  unit-test: # sanity checks before pushing the image
+    runs-on: ubuntu-20.04
+    steps:
+    - name: setup go
+      uses: actions/setup-go@v2
+      with:
+        go-version: 1.14.x
+    - name: checkout
+      uses: actions/checkout@v2
+    - name: unit test
+      run: make test
+    - name: test build
+      run: make build
+
+  container-image:
+    runs-on: ubuntu-20.04
+    needs: [unit-test]
+    env:
+      DOCKER_REPO: docker.pkg.github.com
+      REGISTRY_PATH: plexsystems/konstraint
+    steps:
+    - name: checkout
+      uses: actions/checkout@v2
+    - name: get version tag
+      run: echo TAG_NAME=$(echo ${GITHUB_REF} | rev | cut -d"/" -f1 | rev) >> $GITHUB_ENV
+    - name: docker login
+      env:
+        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+      run: echo "${GITHUB_TOKEN}" | docker login https://${DOCKER_REPO} -u ${GITHUB_ACTOR} --password-stdin
+    - name: docker build
+      run: make docker version=${TAG_NAME}
+    - name: docker push
+      run: make docker-push version=${TAG_NAME} docker-repo=${DOCKER_REPO} registry-path=${REGISTRY_PATH}
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..444505a
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,13 @@
+FROM golang:1.14-alpine AS build
+WORKDIR /go/src/github.com/plexsystems/konstraint
+COPY go.mod .
+COPY go.sum .
+RUN go mod download
+COPY main.go .
+COPY internal/ internal
+ARG KONSTRAINT_VER
+RUN go build -o /konstraint -ldflags="-X 'github.com/plexsystems/konstraint/internal/commands.version=${KONSTRAINT_VER}'"
+
+FROM alpine:3.12
+COPY --from=build /konstraint /usr/bin/konstraint
+ENTRYPOINT ["konstraint"]
\ No newline at end of file
diff --git a/Makefile b/Makefile
index 7fac7a3..3fff4f6 100644
--- a/Makefile
+++ b/Makefile
@@ -30,3 +30,22 @@ release:
 	GOOS=windows GOARCH=amd64 go build -o build/konstraint-windows-amd64.exe -ldflags="-X 'github.com/plexsystems/konstraint/internal/commands.version=$(version)'"
 	GOOS=linux GOARCH=amd64 go build -o build/konstraint-linux-amd64 -ldflags="-X 'github.com/plexsystems/konstraint/internal/commands.version=$(version)'"
 	docker run --rm -v $(shell pwd):/konstraint alpine:3 /bin/ash -c 'cd /konstraint/build && find . -name "konstraint-*" -type f -exec sha256sum {} > checksums.txt \;'
+
+.PHONY: docker
+docker:
+ifeq ($(version),) # this can't be indented because makefiles are picky
+	docker build -t konstraint:latest .
+else
+	docker build -t konstraint:latest -t konstraint:$(version) --build-arg KONSTRAINT_VER=$(version) .
+endif
+
+# The version and the docker repository are required to use the docker-push target
+.PHONY: docker-push
+docker-push:
+	@test $(version)
+	@test $(docker-repo)
+	@test $(registry-path)
+	docker tag konstraint:latest $(docker-repo)/$(registry-path)/konstraint:$(version)
+	docker tag konstraint:latest $(docker-repo)/$(registry-path)/konstraint:latest
+	docker push $(docker-repo)/$(registry-path)/konstraint:$(version)
+	docker push $(docker-repo)/$(registry-path)/konstraint:latest
\ No newline at end of file
diff --git a/examples/container-deny-added-caps/template.yaml b/examples/container-deny-added-caps/template.yaml
index a54fc8d..f5dc282 100755
--- a/examples/container-deny-added-caps/template.yaml
+++ b/examples/container-deny-added-caps/template.yaml
@@ -52,6 +52,15 @@ spec:
           not contains(apiVersion, "/")
       }
       version := gv[count(gv) - 1]
+
+      parameters = input.parameters {
+          is_gatekeeper
+      }
+
+      parameters = data.parameters {
+         not is_gatekeeper
+      }
+
       has_field(obj, field) {
           not object.get(obj, field, "N_DEFINED") == "N_DEFINED"
       }
diff --git a/examples/container-deny-escalation/template.yaml b/examples/container-deny-escalation/template.yaml
index 965436c..5eb8aed 100755
--- a/examples/container-deny-escalation/template.yaml
+++ b/examples/container-deny-escalation/template.yaml
@@ -52,6 +52,15 @@ spec:
           not contains(apiVersion, "/")
       }
       version := gv[count(gv) - 1]
+
+      parameters = input.parameters {
+          is_gatekeeper
+      }
+
+      parameters = data.parameters {
+         not is_gatekeeper
+      }
+
       has_field(obj, field) {
           not object.get(obj, field, "N_DEFINED") == "N_DEFINED"
       }
diff --git a/examples/container-deny-privileged/template.yaml b/examples/container-deny-privileged/template.yaml
index 3ad2053..0304dcc 100755
--- a/examples/container-deny-privileged/template.yaml
+++ b/examples/container-deny-privileged/template.yaml
@@ -52,6 +52,15 @@ spec:
           not contains(apiVersion, "/")
       }
       version := gv[count(gv) - 1]
+
+      parameters = input.parameters {
+          is_gatekeeper
+      }
+
+      parameters = data.parameters {
+         not is_gatekeeper
+      }
+
       has_field(obj, field) {
           not object.get(obj, field, "N_DEFINED") == "N_DEFINED"
       }
diff --git a/examples/container-deny-without-resource-constraints/template.yaml b/examples/container-deny-without-resource-constraints/template.yaml
index b23cfae..63aa133 100644
--- a/examples/container-deny-without-resource-constraints/template.yaml
+++ b/examples/container-deny-without-resource-constraints/template.yaml
@@ -52,6 +52,15 @@ spec:
           not contains(apiVersion, "/")
       }
       version := gv[count(gv) - 1]
+
+      parameters = input.parameters {
+          is_gatekeeper
+      }
+
+      parameters = data.parameters {
+         not is_gatekeeper
+      }
+
       has_field(obj, field) {
           not object.get(obj, field, "N_DEFINED") == "N_DEFINED"
       }
diff --git a/examples/lib/core.rego b/examples/lib/core.rego
index 10655e1..0297d1f 100644
--- a/examples/lib/core.rego
+++ b/examples/lib/core.rego
@@ -39,6 +39,15 @@ group = "core" {
     not contains(apiVersion, "/")
 }
 version := gv[count(gv) - 1]
+
+parameters = input.parameters {
+    is_gatekeeper
+}
+
+parameters = data.parameters {
+   not is_gatekeeper
+}
+
 has_field(obj, field) {
     not object.get(obj, field, "N_DEFINED") == "N_DEFINED"
 }
diff --git a/examples/pod-deny-host-alias/template.yaml b/examples/pod-deny-host-alias/template.yaml
index 74a0d3a..e3ba898 100755
--- a/examples/pod-deny-host-alias/template.yaml
+++ b/examples/pod-deny-host-alias/template.yaml
@@ -52,6 +52,15 @@ spec:
           not contains(apiVersion, "/")
       }
       version := gv[count(gv) - 1]
+
+      parameters = input.parameters {
+          is_gatekeeper
+      }
+
+      parameters = data.parameters {
+         not is_gatekeeper
+      }
+
       has_field(obj, field) {
           not object.get(obj, field, "N_DEFINED") == "N_DEFINED"
       }
diff --git a/examples/pod-deny-host-ipc/template.yaml b/examples/pod-deny-host-ipc/template.yaml
index 954e05a..98c9865 100755
--- a/examples/pod-deny-host-ipc/template.yaml
+++ b/examples/pod-deny-host-ipc/template.yaml
@@ -52,6 +52,15 @@ spec:
           not contains(apiVersion, "/")
       }
       version := gv[count(gv) - 1]
+
+      parameters = input.parameters {
+          is_gatekeeper
+      }
+
+      parameters = data.parameters {
+         not is_gatekeeper
+      }
+
       has_field(obj, field) {
           not object.get(obj, field, "N_DEFINED") == "N_DEFINED"
       }
diff --git a/examples/pod-deny-host-network/template.yaml b/examples/pod-deny-host-network/template.yaml
index 280c2f4..a5dba6f 100755
--- a/examples/pod-deny-host-network/template.yaml
+++ b/examples/pod-deny-host-network/template.yaml
@@ -52,6 +52,15 @@ spec:
           not contains(apiVersion, "/")
       }
       version := gv[count(gv) - 1]
+
+      parameters = input.parameters {
+          is_gatekeeper
+      }
+
+      parameters = data.parameters {
+         not is_gatekeeper
+      }
+
       has_field(obj, field) {
           not object.get(obj, field, "N_DEFINED") == "N_DEFINED"
       }
diff --git a/examples/pod-deny-host-pid/template.yaml b/examples/pod-deny-host-pid/template.yaml
index 752f526..7e5990e 100755
--- a/examples/pod-deny-host-pid/template.yaml
+++ b/examples/pod-deny-host-pid/template.yaml
@@ -52,6 +52,15 @@ spec:
           not contains(apiVersion, "/")
       }
       version := gv[count(gv) - 1]
+
+      parameters = input.parameters {
+          is_gatekeeper
+      }
+
+      parameters = data.parameters {
+         not is_gatekeeper
+      }
+
       has_field(obj, field) {
           not object.get(obj, field, "N_DEFINED") == "N_DEFINED"
       }
diff --git a/examples/pod-deny-without-runasnonroot/template.yaml b/examples/pod-deny-without-runasnonroot/template.yaml
index cd790f1..9ffa9d8 100755
--- a/examples/pod-deny-without-runasnonroot/template.yaml
+++ b/examples/pod-deny-without-runasnonroot/template.yaml
@@ -81,6 +81,15 @@ spec:
           not contains(apiVersion, "/")
       }
       version := gv[count(gv) - 1]
+
+      parameters = input.parameters {
+          is_gatekeeper
+      }
+
+      parameters = data.parameters {
+         not is_gatekeeper
+      }
+
       has_field(obj, field) {
           not object.get(obj, field, "N_DEFINED") == "N_DEFINED"
       }
diff --git a/examples/policies.md b/examples/policies.md
index 64c8721..8d98977 100755
--- a/examples/policies.md
+++ b/examples/policies.md
@@ -640,7 +640,7 @@ violation[msg] {
 
 missing_labels = missing {
     provided := {label | core.labels[label]}
-    required := {label | label := input.parameters.labels[_]}
+    required := {label | label := core.parameters.labels[_]}
     missing := required - provided
 }
 ```
diff --git a/examples/psp-deny-added-caps/template.yaml b/examples/psp-deny-added-caps/template.yaml
index 013ef17..725e37f 100755
--- a/examples/psp-deny-added-caps/template.yaml
+++ b/examples/psp-deny-added-caps/template.yaml
@@ -52,6 +52,15 @@ spec:
           not contains(apiVersion, "/")
       }
       version := gv[count(gv) - 1]
+
+      parameters = input.parameters {
+          is_gatekeeper
+      }
+
+      parameters = data.parameters {
+         not is_gatekeeper
+      }
+
       has_field(obj, field) {
           not object.get(obj, field, "N_DEFINED") == "N_DEFINED"
       }
diff --git a/examples/psp-deny-escalation/template.yaml b/examples/psp-deny-escalation/template.yaml
index 60233ba..bd44919 100755
--- a/examples/psp-deny-escalation/template.yaml
+++ b/examples/psp-deny-escalation/template.yaml
@@ -52,6 +52,15 @@ spec:
           not contains(apiVersion, "/")
       }
       version := gv[count(gv) - 1]
+
+      parameters = input.parameters {
+          is_gatekeeper
+      }
+
+      parameters = data.parameters {
+         not is_gatekeeper
+      }
+
       has_field(obj, field) {
           not object.get(obj, field, "N_DEFINED") == "N_DEFINED"
       }
diff --git a/examples/psp-deny-host-alias/template.yaml b/examples/psp-deny-host-alias/template.yaml
index 65b1ea3..8960c2a 100755
--- a/examples/psp-deny-host-alias/template.yaml
+++ b/examples/psp-deny-host-alias/template.yaml
@@ -52,6 +52,15 @@ spec:
           not contains(apiVersion, "/")
       }
       version := gv[count(gv) - 1]
+
+      parameters = input.parameters {
+          is_gatekeeper
+      }
+
+      parameters = data.parameters {
+         not is_gatekeeper
+      }
+
       has_field(obj, field) {
           not object.get(obj, field, "N_DEFINED") == "N_DEFINED"
       }
diff --git a/examples/psp-deny-host-ipc/template.yaml b/examples/psp-deny-host-ipc/template.yaml
index fc6789c..abfb542 100755
--- a/examples/psp-deny-host-ipc/template.yaml
+++ b/examples/psp-deny-host-ipc/template.yaml
@@ -52,6 +52,15 @@ spec:
           not contains(apiVersion, "/")
       }
       version := gv[count(gv) - 1]
+
+      parameters = input.parameters {
+          is_gatekeeper
+      }
+
+      parameters = data.parameters {
+         not is_gatekeeper
+      }
+
       has_field(obj, field) {
           not object.get(obj, field, "N_DEFINED") == "N_DEFINED"
       }
diff --git a/examples/psp-deny-host-network/template.yaml b/examples/psp-deny-host-network/template.yaml
index 4438ed5..06138a1 100755
--- a/examples/psp-deny-host-network/template.yaml
+++ b/examples/psp-deny-host-network/template.yaml
@@ -52,6 +52,15 @@ spec:
           not contains(apiVersion, "/")
       }
       version := gv[count(gv) - 1]
+
+      parameters = input.parameters {
+          is_gatekeeper
+      }
+
+      parameters = data.parameters {
+         not is_gatekeeper
+      }
+
       has_field(obj, field) {
           not object.get(obj, field, "N_DEFINED") == "N_DEFINED"
       }
diff --git a/examples/psp-deny-host-pid/template.yaml b/examples/psp-deny-host-pid/template.yaml
index e3cfa3d..8a11f75 100755
--- a/examples/psp-deny-host-pid/template.yaml
+++ b/examples/psp-deny-host-pid/template.yaml
@@ -52,6 +52,15 @@ spec:
           not contains(apiVersion, "/")
       }
       version := gv[count(gv) - 1]
+
+      parameters = input.parameters {
+          is_gatekeeper
+      }
+
+      parameters = data.parameters {
+         not is_gatekeeper
+      }
+
       has_field(obj, field) {
           not object.get(obj, field, "N_DEFINED") == "N_DEFINED"
       }
diff --git a/examples/psp-deny-privileged/template.yaml b/examples/psp-deny-privileged/template.yaml
index 06bb2e4..9f21d2a 100755
--- a/examples/psp-deny-privileged/template.yaml
+++ b/examples/psp-deny-privileged/template.yaml
@@ -52,6 +52,15 @@ spec:
           not contains(apiVersion, "/")
       }
       version := gv[count(gv) - 1]
+
+      parameters = input.parameters {
+          is_gatekeeper
+      }
+
+      parameters = data.parameters {
+         not is_gatekeeper
+      }
+
       has_field(obj, field) {
           not object.get(obj, field, "N_DEFINED") == "N_DEFINED"
       }
diff --git a/examples/required-labels/src.rego b/examples/required-labels/src.rego
index 75b226b..e8412bf 100644
--- a/examples/required-labels/src.rego
+++ b/examples/required-labels/src.rego
@@ -20,6 +20,6 @@ violation[msg] {
 
 missing_labels = missing {
     provided := {label | core.labels[label]}
-    required := {label | label := input.parameters.labels[_]}
+    required := {label | label := core.parameters.labels[_]}
     missing := required - provided
 }
diff --git a/examples/required-labels/template.yaml b/examples/required-labels/template.yaml
index 2f03be3..0edb2d9 100755
--- a/examples/required-labels/template.yaml
+++ b/examples/required-labels/template.yaml
@@ -59,6 +59,15 @@ spec:
           not contains(apiVersion, "/")
       }
       version := gv[count(gv) - 1]
+
+      parameters = input.parameters {
+          is_gatekeeper
+      }
+
+      parameters = data.parameters {
+         not is_gatekeeper
+      }
+
       has_field(obj, field) {
           not object.get(obj, field, "N_DEFINED") == "N_DEFINED"
       }
@@ -86,7 +95,7 @@ spec:
 
       missing_labels = missing {
           provided := {label | core.labels[label]}
-          required := {label | label := input.parameters.labels[_]}
+          required := {label | label := core.parameters.labels[_]}
           missing := required - provided
       }
     target: admission.k8s.gatekeeper.sh
diff --git a/examples/role-deny-use-privileged-psp/template.yaml b/examples/role-deny-use-privileged-psp/template.yaml
index 0550a4d..1309f70 100755
--- a/examples/role-deny-use-privileged-psp/template.yaml
+++ b/examples/role-deny-use-privileged-psp/template.yaml
@@ -52,6 +52,15 @@ spec:
           not contains(apiVersion, "/")
       }
       version := gv[count(gv) - 1]
+
+      parameters = input.parameters {
+          is_gatekeeper
+      }
+
+      parameters = data.parameters {
+         not is_gatekeeper
+      }
+
       has_field(obj, field) {
           not object.get(obj, field, "N_DEFINED") == "N_DEFINED"
       }
diff --git a/internal/rego/rego.go b/internal/rego/rego.go
index 1d66082..30774c2 100644
--- a/internal/rego/rego.go
+++ b/internal/rego/rego.go
@@ -302,12 +302,12 @@ func parseDirectory(directory string) ([]Rego, error) {
 }
 
 func getBodyParamNames(rules []*ast.Rule) []string {
-	r := regexp.MustCompile(`input\.parameters\.([a-zA-Z0-9_-]+)`)
+	r := regexp.MustCompile(`(core|input)\.parameters\.([a-zA-Z0-9_-]+)`)
 	var bodyParams []string
 	for _, rule := range rules {
 		matches := r.FindAllStringSubmatch(rule.Body.String(), -1)
 		for _, match := range matches {
-			bodyParams = append(bodyParams, match[1]) // the 0 index is the full match, we only care about the first group
+			bodyParams = append(bodyParams, match[2]) // the 0 index is the full match, we only care about the second group
 		}
 	}
 	bodyParams = dedupe(bodyParams) // possible a param is referenced more than once


Test patch:
diff --git a/examples/container-deny-latest-tag/template.yaml b/examples/container-deny-latest-tag/template.yaml
index 19aa5d6..c906d31 100644
--- a/examples/container-deny-latest-tag/template.yaml
+++ b/examples/container-deny-latest-tag/template.yaml
@@ -52,6 +52,15 @@ spec:
           not contains(apiVersion, "/")
       }
       version := gv[count(gv) - 1]
+
+      parameters = input.parameters {
+          is_gatekeeper
+      }
+
+      parameters = data.parameters {
+         not is_gatekeeper
+      }
+
       has_field(obj, field) {
           not object.get(obj, field, "N_DEFINED") == "N_DEFINED"
       }
diff --git a/examples/required-labels/src_test.rego b/examples/required-labels/src_test.rego
index de16c80..1e5ed15 100644
--- a/examples/required-labels/src_test.rego
+++ b/examples/required-labels/src_test.rego
@@ -13,13 +13,17 @@ test_not_missing {
     count(missing) == 0
 }
 
-test_missing {
+test_missing_gk {
     in := {
-        "metadata": {
-            "labels": {
-                "test": "test"
+        "review": {
+            "object": {
+                "metadata": {
+                    "labels": {
+                        "test": "test"
+                    }
+                }
             }
-        },
+        },        
         "parameters": {
             "labels": ["one", "two"]
         }
@@ -28,3 +32,19 @@ test_missing {
     missing := missing_labels with input as in
     count(missing) == 2
 }
+
+test_missing_not_gk {
+    in := {
+        "metadata": {
+            "labels": {
+                "test": "test"
+            }
+        }
+    }
+    p := {
+        "labels": ["test", "two"]
+    }
+
+    missing := missing_labels with input as in with data.parameters as p
+    count(missing) == 1
+}
diff --git a/internal/rego/rego_test.go b/internal/rego/rego_test.go
index f3a598f..0ec8cbb 100644
--- a/internal/rego/rego_test.go
+++ b/internal/rego/rego_test.go
@@ -162,7 +162,7 @@ func TestGetPolicyID_Null(t *testing.T) {
 	}
 }
 
-func TestGetBodyParamNames(t *testing.T) {
+func TestGetBodyParamNamesFromInput(t *testing.T) {
 	ruleString := `violation[msg] {
 	foo := "bar"
 	bar := input.parameters.baz
@@ -182,6 +182,26 @@ func TestGetBodyParamNames(t *testing.T) {
 	}
 }
 
+func TestGetBodyParamNamesFromCore(t *testing.T) {
+	ruleString := `violation[msg] {
+	foo := "bar"
+	bar := core.parameters.baz
+	baz := core.parameters.foobars[_]
+	box := core.parameters.baz
+}`
+
+	rule, err := ast.ParseRule(ruleString)
+	if err != nil {
+		t.Fatalf("parse rule: %s", err)
+	}
+
+	expected := []string{"baz", "foobars"}
+	actual := getBodyParamNames([]*ast.Rule{rule})
+	if !(reflect.DeepEqual(expected, actual)) {
+		t.Errorf("unexpected bodyParams. expected %+v, actual %+v", expected, actual)
+	}
+}
+
 func TestGetHeaderParams(t *testing.T) {
 	comments := []string{
 		"@title Title",