Skip to content

Commit

Permalink
fix: adding flag to validate rego for templates (#3026) (#3032)
Browse files Browse the repository at this point in the history
Signed-off-by: Jaydip Gabani <[email protected]>
Co-authored-by: Sertaç Özercan <[email protected]>
  • Loading branch information
JaydipGabani and sozercan authored Oct 4, 2023
1 parent 8e2aea5 commit b363b40
Show file tree
Hide file tree
Showing 7 changed files with 333 additions and 3 deletions.
4 changes: 2 additions & 2 deletions demo/k8s-validating-admission-policy/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
This is a demo of a prototype-stage feature and is subject to change.

The demo will not work unless the --experimental-enable-k8s-native-validation is
set.
The demo will not work unless the `--experimental-enable-k8s-native-validation`` is
set. Please set `--validate-template-rego` to `false` if using Gatekeeper version 3.13.1+ but before 3.16.0.

6 changes: 6 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,12 @@ func setupControllers(ctx context.Context, mgr ctrl.Manager, sw *watch.Controlle

cfArgs := []constraintclient.Opt{constraintclient.Targets(&target.K8sValidationTarget{})}

if *webhook.ValidateTemplateRego && *enableK8sCel {
err := fmt.Errorf("cannot validate template rego when K8s cel is enabled. Please disable K8s cel by setting --experimental-enable-k8s-native-validation=false or disable template rego validation by setting --validate-template-rego=false")
setupLog.Error(err, "unable to set up OPA and K8s native drivers")
return err
}

if *enableK8sCel {
// initialize K8sValidation
k8sDriver, err := k8scel.New()
Expand Down
21 changes: 20 additions & 1 deletion pkg/webhook/policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (
externaldataUnversioned "github.com/open-policy-agent/frameworks/constraint/pkg/apis/externaldata/unversioned"
constraintclient "github.com/open-policy-agent/frameworks/constraint/pkg/client"
"github.com/open-policy-agent/frameworks/constraint/pkg/client/drivers"
"github.com/open-policy-agent/frameworks/constraint/pkg/client/drivers/rego"
"github.com/open-policy-agent/frameworks/constraint/pkg/core/templates"
"github.com/open-policy-agent/frameworks/constraint/pkg/externaldata"
rtypes "github.com/open-policy-agent/frameworks/constraint/pkg/types"
Expand Down Expand Up @@ -68,7 +69,10 @@ import (
// https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#response
const httpStatusWarning = 299

var maxServingThreads = flag.Int("max-serving-threads", -1, "cap the number of threads handling non-trivial requests, -1 caps the number of threads to GOMAXPROCS. Defaults to -1.")
var (
ValidateTemplateRego = flag.Bool("validate-template-rego", true, "validate Rego code for constraint templates. Defaults to true. This flag will be removed in Gatekeeper v3.16 and cannot be used if `experimental-enable-k8s-native-validation` flag is set. Use Gator to validate in shift left manner to avoid impact with this behavior change.). Use Gator to validate in shift left manner to avoid impact with this behavior change.")
maxServingThreads = flag.Int("max-serving-threads", -1, "cap the number of threads handling non-trivial requests, -1 caps the number of threads to GOMAXPROCS. Defaults to -1.")
)

func init() {
AddToManagerFuncs = append(AddToManagerFuncs, AddPolicyWebhook)
Expand Down Expand Up @@ -382,6 +386,21 @@ func (h *validationHandler) validateTemplate(ctx context.Context, req *admission
return true, err
}

// TODO: This is a temporary check for rego to give enough time to users to migrate to gator for validation. To be removed before 3.16.
if *ValidateTemplateRego {
// Create a temporary Driver and attempt to add the Template to it. This
// ensures the Rego code both parses and compiles.
d, err := rego.New()
if err != nil {
return false, fmt.Errorf("unable to create Driver: %w", err)
}

err = d.AddTemplate(ctx, unversioned)
if err != nil {
return true, err
}
}

return false, nil
}

Expand Down
2 changes: 2 additions & 0 deletions website/docs/constrainttemplates.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ ConstraintTemplates define a way to validate some set of Kubernetes objects in G
1. [Rego](https://www.openpolicyagent.org/docs/latest/#rego) code that defines a policy violation
2. The schema of the accompanying `Constraint` object, which represents an instantiation of a `ConstraintTemplate`

> ❗ Validation of Rego for constraint templates is enabled by default. Set `validate-template-rego` flag to `false` to disable rego validation if you want to use `experimental-enable-k8s-native-validation` Kubernetes CEL based policies as well. This flag will be removed from Gatekeeper 3.16 and later, please make use of [Gator](https://open-policy-agent.github.io/gatekeeper/website/docs/gator) to validate constraint template in shift left manner to avoid any impact with this behavior change.
## `v1` Constraint Template

In release version 3.6.0, Gatekeeper included the `v1` version of `ConstraintTemplate`. Unlike past versions of `ConstraintTemplate`, `v1` requires the Constraint schema section to be [structural](https://kubernetes.io/blog/2019/06/20/crd-structural-schema/).
Expand Down
71 changes: 71 additions & 0 deletions website/docs/validating-admission-policy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
---
id: validating-admission-policy
title: Integration with Kubernetes Validating Admission Policy
---

`Feature State`: Gatekeeper version v3.13+ (pre-alpha)

> ❗ This feature is pre-alpha, subject to change (feedback is welcome!). It is disabled by default. To enable the feature,
> set the `experimental-enable-k8s-native-validation` flag to true and use the [development build of Gatekeeper](https://open-policy-agent.github.io/gatekeeper/website/docs/install/#deploying-a-release-using-development-image). Do not use this feature with `validate-template-rego` flag enabled, as the policies with CEL would get rejected with Rego compilation error.
## Description

This feature allows Gatekeeper to integrate with Kubernetes Validating Admission Policy based on [Common Expression Language (CEL)](https://github.com/google/cel-spec), a declarative, in-process admission control alternative to validating admission webhooks.

## Motivations

The Validating Admission Policy feature (disabled by default) was introduced as an alpha feature to Kubernetes v1.26, beta in v1.28. Some of the benefits include:
- in-tree/native in-process
- reduce admission request latency
- improve reliability and availability
- able to fail closed without impacting availability
- avoid the operational burden of webhooks

To reduce policy fragmentation and simplify the user experience by standardizing the policy experience. We have created an abstraction layer that provides multi-language (e.g. Rego and CEL), multi-target policy enforcement to allow for portable policies and coexistence of numerous policy implementations.

The [Constraint Framework](https://github.com/open-policy-agent/frameworks/tree/master/constraint) is the library that underlies Gatekeeper. It provides the execution flow Gatekeeper uses to render a decision to the API server. It also provides abstractions that allow us to define constraint templates and constraints: Engine, Enforcement Points, and Targets.

Together with Gatekeeper and [gator CLI](gator.md), you can get admission, audit, and shift left validations for both CEL-based Validating Admission Policy and OPA Rego policies, even for clusters that do not support Validating Admission Policy feature yet.

## Example Constraint Template
To see how it works, check out this [demo](https://github.com/open-policy-agent/gatekeeper/tree/master/demo/k8s-validating-admission-policy)

Example `K8sRequiredLabels` constraint template using the `K8sNativeValidation` engine and CEL expressions that requires resources to contain specified labels with values matching provided regular expressions. A similar policy written in Rego can be seen [here](https://open-policy-agent.github.io/gatekeeper-library/website/validation/requiredlabels)

```yaml
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8srequiredlabels
spec:
crd:
spec:
names:
kind: K8sRequiredLabels
validation:
# Schema for the `parameters` field
openAPIV3Schema:
type: object
properties:
message:
type: string
labels:
type: array
items:
type: object
properties:
key:
type: string
allowedRegex:
type: string
targets:
- target: admission.k8s.gatekeeper.sh
code:
- engine: K8sNativeValidation
source:
validations:
- expression: "variables.params.labels.all(entry, has(object.metadata.labels) && entry.key in object.metadata.labels)"
messageExpression: '"missing required label, requires all of: " + variables.params.labels.map(entry, entry.key).join(", ")'
- expression: "!variables.params.labels.exists(entry, has(object.metadata.labels) && entry.key in object.metadata.labels && !string(object.metadata.labels[entry.key]).matches(string(entry.allowedRegex)))"
message: "regex mismatch"
```
161 changes: 161 additions & 0 deletions website/versioned_docs/version-v3.13.x/constrainttemplates.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
---
id: constrainttemplates
title: Constraint Templates
---

ConstraintTemplates define a way to validate some set of Kubernetes objects in Gatekeeper's Kubernetes [admission controller](https://kubernetes.io/blog/2019/03/21/a-guide-to-kubernetes-admission-controllers/). They are made of two main elements:

1. [Rego](https://www.openpolicyagent.org/docs/latest/#rego) code that defines a policy violation
2. The schema of the accompanying `Constraint` object, which represents an instantiation of a `ConstraintTemplate`

> ❗ Validation of Rego for constraint templates is enabled by default. Set `validate-template-rego` flag to `false` to disable rego validation if you want to use `experimental-enable-k8s-native-validation` Kubernetes CEL based policies as well. This flag will be removed from Gatekeeper 3.16 and later, please make use of [Gator](https://open-policy-agent.github.io/gatekeeper/website/docs/gator) to validate constraint template in shift left manner to avoid any impact with this behavior change.
## `v1` Constraint Template

In release version 3.6.0, Gatekeeper included the `v1` version of `ConstraintTemplate`. Unlike past versions of `ConstraintTemplate`, `v1` requires the Constraint schema section to be [structural](https://kubernetes.io/blog/2019/06/20/crd-structural-schema/).

Structural schemas have a variety of [requirements](https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#specifying-a-structural-schema). One such requirement is that the `type` field be defined for each level of the schema.

For example, users of Gatekeeper may recognize the `k8srequiredlabels` ConstraintTemplate, defined here in version `v1beta1`:

```yaml
apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
name: k8srequiredlabels
spec:
crd:
spec:
names:
kind: K8sRequiredLabels
validation:
# Schema for the `parameters` field
openAPIV3Schema:
properties:
labels:
type: array
items:
type: string
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8srequiredlabels
violation[{"msg": msg, "details": {"missing_labels": missing}}] {
provided := {label | input.review.object.metadata.labels[label]}
required := {label | label := input.parameters.labels[_]}
missing := required - provided
count(missing) > 0
msg := sprintf("you must provide labels: %v", [missing])
}
```
The `parameters` field schema (`spec.crd.spec.validation.openAPIV3Schema`) is _not_ structural. Notably, it is missing the `type:` declaration:

```yaml
openAPIV3Schema:
# missing type
properties:
labels:
type: array
items:
type: string
```

This schema is _invalid_ by default in a `v1` ConstraintTemplate. Adding the `type` information makes the schema valid:

```yaml
openAPIV3Schema:
type: object
properties:
labels:
type: array
items:
type: string
```

For more information on valid types in JSONSchemas, see the [JSONSchema documentation](https://json-schema.org/understanding-json-schema/reference/type.html).

## Why implement this change?

Structural schemas are required in version `v1` of `CustomResourceDefinition` resources, which underlie ConstraintTemplates. Requiring the same in ConstraintTemplates puts Gatekeeper in line with the overall direction of Kubernetes.

Beyond this alignment, structural schemas yield significant usability improvements. The schema of a ConstraintTemplate's associated Constraint is both more visible and type validated.

As the data types of Constraint fields are defined in the ConstraintTemplate, the API server will reject a Constraint with an incorrect `parameters` field. Previously, the API server would ingest it and simply not pass those `parameters` to Gatekeeper. This experience was confusing for users, and is noticeably improved by structural schemas.

For example, see this incorrectly defined `k8srequiredlabels` Constraint:

```yaml
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredLabels
metadata:
name: ns-must-have-gk
spec:
match:
kinds:
- apiGroups: [""]
kinds: ["Namespace"]
parameters:
# Note that "labels" is now contained in an array item, rather than an object key under "parameters"
- labels: ["gatekeeper"]
```

In a `v1beta1` ConstraintTemplate, this Constraint would be ingested successfully. However, it would not work. The creation of a new namespace, `foobar`, would succeed, even in the absence of the `gatekeeper` label:

```shell
$ kubectl create ns foobar
namespace/foobar created
```

This is incorrect. We'd expect this to fail:

```shell
$ kubectl create ns foobar
Error from server ([ns-must-have-gk] you must provide labels: {"gatekeeper"}): admission webhook "validation.gatekeeper.sh" denied the request: [ns-must-have-gk] you must provide labels: {"gatekeeper"}
```

The structural schema requirement _prevents this mistake_. The aforementioned `type: object` declaration would prevent the API server from accepting the incorrect `k8srequiredlabels` Constraint.

```shell
# Apply the Constraint with incorrect parameters schema
$ cat << EOF | kubectl apply -f -
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredLabels
metadata:
name: ns-must-have-gk
spec:
match:
kinds:
- apiGroups: [""]
kinds: ["Namespace"]
parameters:
# Note that "labels" is now an array item, rather than an object
- labels: ["gatekeeper"]
EOF
The K8sRequiredLabels "ns-must-have-gk" is invalid: spec.parameters: Invalid value: "array": spec.parameters in body must be of type object: "array"
```

Fixing the incorrect `parameters` section would then yield a successful ingestion and a working Constraint.

```shell
$ cat << EOF | kubectl apply -f -
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredLabels
metadata:
name: ns-must-have-gk
spec:
match:
kinds:
- apiGroups: [""]
kinds: ["Namespace"]
parameters:
labels: ["gatekeeper"]
EOF
k8srequiredlabels.constraints.gatekeeper.sh/ns-must-have-gk created
```

```shell
$ kubectl create ns foobar
Error from server ([ns-must-have-gk] you must provide labels: {"gatekeeper"}): admission webhook "validation.gatekeeper.sh" denied the request: [ns-must-have-gk] you must provide labels: {"gatekeeper"}
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
---
id: validating-admission-policy
title: Integration with Kubernetes Validating Admission Policy
---

`Feature State`: Gatekeeper version v3.13+ (pre-alpha)

> ❗ This feature is pre-alpha, subject to change (feedback is welcome!). It is disabled by default. To enable the feature,
> set the `experimental-enable-k8s-native-validation` flag to true and use the [development build of Gatekeeper](https://open-policy-agent.github.io/gatekeeper/website/docs/install/#deploying-a-release-using-development-image). Do not use this feature with `validate-template-rego` flag enabled, as the policies with CEL would get rejected with Rego compilation error.
## Description

This feature allows Gatekeeper to integrate with Kubernetes Validating Admission Policy based on [Common Expression Language (CEL)](https://github.com/google/cel-spec), a declarative, in-process admission control alternative to validating admission webhooks.

## Motivations

The Validating Admission Policy feature (disabled by default) was introduced as an alpha feature to Kubernetes v1.26, beta in v1.28. Some of the benefits include:
- in-tree/native in-process
- reduce admission request latency
- improve reliability and availability
- able to fail closed without impacting availability
- avoid the operational burden of webhooks

To reduce policy fragmentation and simplify the user experience by standardizing the policy experience. We have created an abstraction layer that provides multi-language (e.g. Rego and CEL), multi-target policy enforcement to allow for portable policies and coexistence of numerous policy implementations.

The [Constraint Framework](https://github.com/open-policy-agent/frameworks/tree/master/constraint) is the library that underlies Gatekeeper. It provides the execution flow Gatekeeper uses to render a decision to the API server. It also provides abstractions that allow us to define constraint templates and constraints: Engine, Enforcement Points, and Targets.

Together with Gatekeeper and [gator CLI](gator.md), you can get admission, audit, and shift left validations for both CEL-based Validating Admission Policy and OPA Rego policies, even for clusters that do not support Validating Admission Policy feature yet.

## Example Constraint Template
To see how it works, check out this [demo](https://github.com/open-policy-agent/gatekeeper/tree/master/demo/k8s-validating-admission-policy)

Example `K8sRequiredLabels` constraint template using the `K8sNativeValidation` engine and CEL expressions that requires resources to contain specified labels with values matching provided regular expressions. A similar policy written in Rego can be seen [here](https://open-policy-agent.github.io/gatekeeper-library/website/validation/requiredlabels)

```yaml
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8srequiredlabels
spec:
crd:
spec:
names:
kind: K8sRequiredLabels
validation:
# Schema for the `parameters` field
openAPIV3Schema:
type: object
properties:
message:
type: string
labels:
type: array
items:
type: object
properties:
key:
type: string
allowedRegex:
type: string
targets:
- target: admission.k8s.gatekeeper.sh
code:
- engine: K8sNativeValidation
source:
validations:
- expression: "variables.params.labels.all(entry, has(object.metadata.labels) && entry.key in object.metadata.labels)"
messageExpression: '"missing required label, requires all of: " + variables.params.labels.map(entry, entry.key).join(", ")'
- expression: "!variables.params.labels.exists(entry, has(object.metadata.labels) && entry.key in object.metadata.labels && !string(object.metadata.labels[entry.key]).matches(string(entry.allowedRegex)))"
message: "regex mismatch"
```

0 comments on commit b363b40

Please sign in to comment.