Repository: plexsystems/konstraint
Pull request #71 (2020-09-30 20:15:12)

Add support for setting the constraint enforcement action per policy
In some scenarios, users may wish to set the enforcement action per policy rather than globally with the `--dryrun` flag. This adds support this via a `@enforcement` tag in the comment header block. If no enforcement is specified, we default to `deny` to align with Gatekeeper's handling of this setting and so we do not break any existing workflows using Konstraint. Additionally, policies with their enforcement action set to `dryrun` will be skipped in the policies documentation generation.


Patch:
diff --git a/docs/cli/konstraint.md b/docs/cli/konstraint.md
index 78a10bb..50c6384 100644
--- a/docs/cli/konstraint.md
+++ b/docs/cli/konstraint.md
@@ -17,4 +17,4 @@ A tool to create and manage Gatekeeper CRDs from Rego
 * [konstraint create](konstraint_create.md)	 - Create Gatekeeper constraints from Rego policies
 * [konstraint doc](konstraint_doc.md)	 - Generate documentation from Rego policies
 
-###### Auto generated by spf13/cobra on 26-Aug-2020
+###### Auto generated by spf13/cobra on 30-Sep-2020
diff --git a/docs/cli/konstraint_create.md b/docs/cli/konstraint_create.md
index 939721c..babde9e 100644
--- a/docs/cli/konstraint_create.md
+++ b/docs/cli/konstraint_create.md
@@ -26,7 +26,7 @@ Create constraints with the Gatekeeper enforcement action set to dryrun
 ### Options
 
 ```
-  -d, --dryrun          Sets the enforcement action of the constraints to dryrun
+  -d, --dryrun          Sets the enforcement action of the constraints to dryrun, overriding the @enforcement tag
   -h, --help            help for create
   -o, --output string   Specify an output directory for the Gatekeeper resources
 ```
@@ -35,4 +35,4 @@ Create constraints with the Gatekeeper enforcement action set to dryrun
 
 * [konstraint](konstraint.md)	 - Konstraint
 
-###### Auto generated by spf13/cobra on 26-Aug-2020
+###### Auto generated by spf13/cobra on 30-Sep-2020
diff --git a/docs/cli/konstraint_doc.md b/docs/cli/konstraint_doc.md
index 674b3e7..f0ff341 100644
--- a/docs/cli/konstraint_doc.md
+++ b/docs/cli/konstraint_doc.md
@@ -35,4 +35,4 @@ Set the URL where the policies are hosted at
 
 * [konstraint](konstraint.md)	 - Konstraint
 
-###### Auto generated by spf13/cobra on 26-Aug-2020
+###### Auto generated by spf13/cobra on 30-Sep-2020
diff --git a/docs/constraint_creation.md b/docs/constraint_creation.md
index 50a904e..18695a4 100644
--- a/docs/constraint_creation.md
+++ b/docs/constraint_creation.md
@@ -48,12 +48,15 @@ This comment block should:
 - Include a human readable description of what the policy does.
 - Set the matchers used when generating the Constraints.
 
+It may also specify the enforcment action (either `deny` or `dryrun`) that Gatekeeper should take when a resource violates the constraint. If no enforcement action is specified, Konstraint defaults to using `deny` to align with Gatekeeper's default action. If the enforcement is set to `dryrun`, the policy will be skipped in the documentation generation.
+
 ```rego
 # @title Pods must not run with access to the host IPC
 #
 # Pods that are allowed to access the host IPC can read memory of
 # the other containers, breaking that security boundary.
 #
+# @enforcement deny
 # @kinds apps/DaemonSet apps/Deployment apps/StatefulSet core/Pod
 package pod_deny_host_ipc
 
diff --git a/internal/commands/create.go b/internal/commands/create.go
index 37a5b3b..9a08f90 100644
--- a/internal/commands/create.go
+++ b/internal/commands/create.go
@@ -49,7 +49,7 @@ Create constraints with the Gatekeeper enforcement action set to dryrun
 	}
 
 	cmd.PersistentFlags().StringP("output", "o", "", "Specify an output directory for the Gatekeeper resources")
-	cmd.PersistentFlags().BoolP("dryrun", "d", false, "Sets the enforcement action of the constraints to dryrun")
+	cmd.PersistentFlags().BoolP("dryrun", "d", false, "Sets the enforcement action of the constraints to dryrun, overriding the @enforcement tag")
 
 	return &cmd
 }
@@ -140,8 +140,9 @@ func getConstraint(violation rego.Rego) (unstructured.Unstructured, error) {
 	constraint.SetGroupVersionKind(gvk)
 	constraint.SetName(violation.Name())
 
+	// the dryrun flag overrides any enforcement action specified in the rego header
 	dryrun := viper.GetBool("dryrun")
-	if dryrun {
+	if dryrun || violation.Enforcement() == "dryrun" {
 		if err := unstructured.SetNestedField(constraint.Object, "dryrun", "spec", "enforcementAction"); err != nil {
 			return unstructured.Unstructured{}, fmt.Errorf("set constraint dryrun: %w", err)
 		}
diff --git a/internal/commands/document.go b/internal/commands/document.go
index f7a5209..b2ca704 100644
--- a/internal/commands/document.go
+++ b/internal/commands/document.go
@@ -101,6 +101,10 @@ func getDocumentation(path string, outputDirectory string) (map[rego.Severity][]
 			continue
 		}
 
+		if policy.Enforcement() == "dryrun" {
+			continue
+		}
+
 		var url string
 		if viper.GetString("url") != "" {
 			url = viper.GetString("url") + "/" + policy.Path()
diff --git a/internal/rego/rego.go b/internal/rego/rego.go
index 54832ce..5bc53d0 100644
--- a/internal/rego/rego.go
+++ b/internal/rego/rego.go
@@ -129,6 +129,22 @@ func (r Rego) Title() string {
 	return trimString(title)
 }
 
+// Enforcement returns the enforcement action in the header comment
+// Defaults to deny if no enforcement action is specified
+func (r Rego) Enforcement() string {
+	enforcement := "deny"
+	for _, comment := range r.comments {
+		if !strings.Contains(comment, "@enforcement") {
+			continue
+		}
+
+		enforcement = strings.SplitAfter(comment, "@enforcement")[1]
+		break
+	}
+
+	return trimString(enforcement)
+}
+
 // Description returns the entire description
 // found in the header comment of the rego file.
 func (r Rego) Description() string {


Test patch:
diff --git a/internal/rego/rego_test.go b/internal/rego/rego_test.go
index 54fa92c..5507e56 100644
--- a/internal/rego/rego_test.go
+++ b/internal/rego/rego_test.go
@@ -105,3 +105,27 @@ third`
 		t.Errorf("unexpected Source. expected %v, actual %v", expected, actual)
 	}
 }
+
+func TestEnforcement(t *testing.T) {
+	comments := []string{
+		"@title Test",
+		"description",
+		"@enforcement dryrun",
+		"@kinds apps/Deployment",
+	}
+	rego := Rego{
+		comments: comments,
+	}
+
+	actual := rego.Enforcement()
+	const expected = "dryrun"
+	if actual != expected {
+		t.Errorf("unexpected Enforcement. expected %v, actual %v", expected, actual)
+	}
+
+	actualDefault := Rego{}.Enforcement()
+	const expectedDefault = "deny"
+	if actualDefault != expectedDefault {
+		t.Errorf("unexpected Enforcement. expected %v, actual %v", expectedDefault, actualDefault)
+	}
+}