Skip to content

feat: use hooks as a validating webhook handlers #223

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 19 commits into from
Jan 12, 2021
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/code-checks.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,4 @@ jobs:
- name: codespell
run: |
pip install codespell==v1.17.1
codespell --skip=".git,go.mod,go.sum,*.log,*.gif,*.png" -L witht,eventtypes,uint,uptodate
codespell --skip=".git,go.mod,go.sum,*.log,*.gif,*.png" -L witht,eventtypes,uint,uptodate,keyPair
213 changes: 213 additions & 0 deletions BINDING_VALIDATING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
# kubernetesValidating

This binding transforms a hook into a handler for ValidatingWebhookConfiguration. The Shell-operator creates ValidatingWebhookConfiguration, starts HTTPS server and use hooks to handle [AdmissionReview requests](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#request).

## Syntax

```yaml
configVersion: v1
onStartup: 10
kubernetes:
- name: myCrdObjects
...
kubernetesValidating:
- name: myCrdValidator
includeSnapshotsFrom: ["myCrdObjects"]
failurePolicy: Ignore | Fail (default)
labelSelector: # equivalent of objectSelector
matchLabels:
label1: value1
...
namespace:
labelSelector: # equivalent of namespaceSelector
matchLabels:
label1: value1
...
matchExpressions:
- key: environment
operator: In
values: ["prod","staging"]
rules:
- apiVersions:
- v1
apiGroups:
- stable.example.com
resources:
- CronTab
operations:
- "*"
- operations: ["CREATE", "UPDATE"]
apiGroups: ["apps"]
apiVersions: ["v1", "v1beta1"]
resources: ["deployments", "replicasets"]
scope: "Namespaced"
sideEffects: None (default) | NoneOnDryRun
timeoutSeconds: 2 (default is 10)
```

## Parameters

- `name` is a required parameter. It should be a domain with at least three segments separated by dots.

- `includeSnapshotsFrom` — an array of names of `kubernetes` bindings in a hook. When specified, a list of monitored objects from that bindings will be added to the binding context in a `snapshots` field.

- `labelSelector` — [standard](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.17/#labelselector-v1-meta) selector of objects by labels (examples [of use](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels)). See [objectSelector](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#matching-requests-objectselector).

- `namespace.labelSelector` — this filter works like `labelSelector` but for namespaces. See [namespaceSelector](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#matching-requests-namespaceselector).

- `rules` — a list of rules used to determine if a request to the Kubernetes API server should be sent to the hook. See [Rules](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#matching-requests-rules).

- `failurePolicy` — defines how errors from the hook are handled. See [Failure policy](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#failure-policy). Default is `Fail`.

- `sideEffects` — determine whether the hook is `dryRun`-aware. See [side effects](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#side-effects) documentation. Default is `None`.

- `timeoutSeconds` — a seconds API server should wait for a hook to respond before treating the call as a failure. See [timeouts](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#timeouts). Default is 10 (seconds).

As you can see, it is the close copy of a [Webhook configuration](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#webhook-configuration). Differences are:
- `objectSelector` is a `labelSelector` as in the `kubernetes` binding.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not so important, but this works only in Kubernetes 1.15+

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

- `namespaceSelector` is a `namespace.labelSelector` as in the `kubernetes` binding.
- `clientConfig` is managed by the Shell-operator. You should provide a Service for the Shell-operator HTTPS endpoint. See example [204-validating-webhook](./examples/204-validating-webhook) for possible solution.
- `matchPolicy` is always "Equivalent". See [Matching requests: matchPolicy](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#matching-requests-matchpolicy).
- there are additional fields `group` and `includeSnapshotsFrom` to include snapshots in the binding context.

## Example

```
configVersion: v1
kubernetesValidating:
- name: private-repo-policy.example.com
rules:
- apiGroups: ["stable.example.com"]
apiVersions: ["v1"]
operations: ["CREATE"]
resources: ["crontabs"]
scope: "Namespaced"
```

The Shell-operator will execute hook with this configuration on every creation of CronTab object.

See example [204-validating-webhook](./examples/204-validating-webhook).

## Hook input and output

The hook receives a binding context and should return response in `$VALIDATING_RESPONSE_PATH`.

$BINDING_CONTEXT_PATH file example:

```yaml
[{
"binding":"myCrdValidator",
"type":"Validating",
"snapshots": { ... }
# AdmissionReview object
"review": {
"apiVersion": "admission.k8s.io/v1",
"kind": "AdmissionReview",
"request": {
# Random uid uniquely identifying this admission call
"uid": "705ab4f5-6393-11e8-b7cc-42010a800002",

# Fully-qualified group/version/kind of the incoming object
"kind": {"group":"autoscaling","version":"v1","kind":"Scale"},
# Fully-qualified group/version/kind of the resource being modified
"resource": {"group":"apps","version":"v1","resource":"deployments"},
# subresource, if the request is to a subresource
"subResource": "scale",

# Fully-qualified group/version/kind of the incoming object in the original request to the API server.
# This only differs from `kind` if the webhook specified `matchPolicy: Equivalent` and the
# original request to the API server was converted to a version the webhook registered for.
"requestKind": {"group":"autoscaling","version":"v1","kind":"Scale"},
# Fully-qualified group/version/kind of the resource being modified in the original request to the API server.
# This only differs from `resource` if the webhook specified `matchPolicy: Equivalent` and the
# original request to the API server was converted to a version the webhook registered for.
"requestResource": {"group":"apps","version":"v1","resource":"deployments"},
# subresource, if the request is to a subresource
# This only differs from `subResource` if the webhook specified `matchPolicy: Equivalent` and the
# original request to the API server was converted to a version the webhook registered for.
"requestSubResource": "scale",

# Name of the resource being modified
"name": "my-deployment",
# Namespace of the resource being modified, if the resource is namespaced (or is a Namespace object)
"namespace": "my-namespace",

# operation can be CREATE, UPDATE, DELETE, or CONNECT
"operation": "UPDATE",

"userInfo": {
# Username of the authenticated user making the request to the API server
"username": "admin",
# UID of the authenticated user making the request to the API server
"uid": "014fbff9a07c",
# Group memberships of the authenticated user making the request to the API server
"groups": ["system:authenticated","my-admin-group"],
# Arbitrary extra info associated with the user making the request to the API server.
# This is populated by the API server authentication layer and should be included
# if any SubjectAccessReview checks are performed by the webhook.
"extra": {
"some-key":["some-value1", "some-value2"]
}
},

# object is the new object being admitted.
# It is null for DELETE operations.
"object": {"apiVersion":"autoscaling/v1","kind":"Scale",...},
# oldObject is the existing object.
# It is null for CREATE and CONNECT operations.
"oldObject": {"apiVersion":"autoscaling/v1","kind":"Scale",...},
# options contains the options for the operation being admitted, like meta.k8s.io/v1 CreateOptions, UpdateOptions, or DeleteOptions.
# It is null for CONNECT operations.
"options": {"apiVersion":"meta.k8s.io/v1","kind":"UpdateOptions",...},

# dryRun indicates the API request is running in dry run mode and will not be persisted.
# Webhooks with side effects should avoid actuating those side effects when dryRun is true.
# See http://k8s.io/docs/reference/using-api/api-concepts/#make-a-dry-run-request for more details.
"dryRun": false
}
}
}]
```

Response example:
```
cat <<EOF >> $VALIDATING_RESPONSE_PATH
{"allowed": true}
EOF
```

Deny object creation and explain why:
```
cat <<EOF > $VALIDATING_RESPONSE_PATH
{"allowed": false, "message": "You cannot do this because it is Tuesday and your name starts with A"}
EOF
```

User will see an error message:

```
Error from server: admission webhook "policy.example.com" denied the request: You cannot do this because it is Tuesday and your name starts with A
```

Empty or invalid $VALIDATING_RESPONSE_PATH file is considered as `"allowed": false` with a short message about the problem and a more verbose error in the log.

## HTTP server and Kubernetes configuration

Shell-operator should create an HTTP endpoint with TLS support and register endpoints in the ValidatingWebhookConfiguration resource.

There should be a Service for shell-operator (see [Availability](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#availability)).

Command line options:

```
--validating-webhook-server-cert="/validating-certs/server.crt"
A path to a server certificate for ValidatingWebhook. Can be set with $VALIDATING_WEBHOOK_SERVER_CERT.
--validating-webhook-server-key="/validating-certs/server-key.pem"
A path to a server private key for ValidatingWebhook. Can be set with $VALIDATING_WEBHOOK_SERVER_KEY.
--validating-webhook-cluster-ca="/validating-certs/cluster-ca.pem"
A path to a cluster ca bundle for ValidatingWebhook. Can be set with $VALIDATING_WEBHOOK_CLUSTER_CA.
--validating-webhook-client-ca=VALIDATING-WEBHOOK-CLIENT-CA ...
A path to a server certificate for ValidatingWebhook. Can be set with $VALIDATING_WEBHOOK_CLIENT_CA.
--validating-webhook-service-name=VALIDATING-WEBHOOK-SERVICE-NAME ...
A name of a service in front of a shell-operator. Can be set with $VALIDATING_WEBHOOK_SERVICE_NAME.
```
39 changes: 26 additions & 13 deletions HOOKS.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,14 @@ schedule:
kubernetes:
- {KUBERNETES_PARAMETERS}
- {KUBERNETES_PARAMETERS}
kubernetesValidating:
- {VALIDATING_PARAMETERS}
- {VALIDATING_PARAMETERS}
```

or in JSON format:

```json
```yaml
{
"configVersion": "v1",
"onStartup": STARTUP_ORDER,
Expand All @@ -61,19 +64,23 @@ or in JSON format:
{SCHEDULE_PARAMETERS}
],
"kubernetes": [
{KUBERNETES_PARAMETERS},
{KUBERNETES_PARAMETERS}
{KUBERNETES_PARAMETERS},
{KUBERNETES_PARAMETERS}
],
"kubernetesValidating": [
{VALIDATING_PARAMETERS},
{VALIDATING_PARAMETERS}
]
}
```

`configVersion` field specifies a version of configuration schema. The latest schema version is **v1** and it is described below.

Event binding is an event type ("onStartup", "schedule" or "kubernetes") plus parameters required for a subscription.
Event binding is an event type (one of "onStartup", "schedule", "kubernetes" or "kubernetesValidating") plus parameters required for a subscription.

### onStartup

Use this binding type to execute a hook at the Shell-operator’ startup.
Use this binding type to execute a hook at the Shell-operator’s startup.

Syntax:

Expand All @@ -84,13 +91,13 @@ onStartup: ORDER

Parameters:

`ORDER` — an integer value that specifies an execution order. When added to the "main" queue, the hooks will be sorted by this value and then alphabetically by file name.
`ORDER` — an integer value that specifies an execution order. "OnStartup" hooks will be sorted by this value and then alphabetically by file name.

### schedule

Scheduled execution. You can bind a hook to any number of schedules.

Syntax:
#### Syntax

```yaml
configVersion: v1
Expand All @@ -117,7 +124,7 @@ schedule:
...
```

Parameters:
#### Parameters

- `name` — is an optional identifier. It is used to distinguish between multiple schedules during runtime. For more information see [binding context](#binding-context).

Expand All @@ -135,7 +142,7 @@ Parameters:

Run a hook on a Kubernetes object changes.

Syntax:
#### Syntax

```yaml
configVersion: v1
Expand Down Expand Up @@ -191,7 +198,7 @@ kubernetes:
# ...
```

Parameters:
#### Parameters

- `name` is an optional identifier. It is used to distinguish different bindings during runtime. See also [binding context](#binding-context).

Expand Down Expand Up @@ -236,7 +243,7 @@ Parameters:

- `group` — a key that define a group of `schedule` and `kubernetes` bindings. See [grouping](#an-example-of-a-binding-context-with-group).

Example:
#### Example

```yaml
configVersion: v1
Expand All @@ -257,7 +264,7 @@ kubernetes:

This hook configuration will execute hook on each change in labels of pods labeled with `myLabel=myLabelValue` in "default" namespace. The binding context will contain all pods with `myLabel=myLabelValue` from "default" namespace.

#### Extra notes
#### Notes

##### default Namespace

Expand All @@ -278,7 +285,7 @@ This filter is used to *ignore* superfluous "Modified" events, *and* to *exclude

The result of applying the filter to the event's object is passed to the hook in a `filterResult` field of a [binding context](#binding-context).

You can use `JQ_LIBRARY_PATH` environment variable to set a path with `jq` modules. Also, Shell-operator uses `jq` release 1.6 so you can check your filters with a binary of that version.
You can use `JQ_LIBRARY_PATH` environment variable to set a path with `jq` modules.

##### Added != Object created

Expand Down Expand Up @@ -315,6 +322,12 @@ fieldSelector:

Objects should match all expressions defined in `fieldSelector` and `labelSelector`, so, for example, multiple `fieldSelector` expressions with `metadata.name` field and different values will not match any object.

### kubernetesValidating

Use a hook as handler for [ValidationWebhookConfiguration](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers).

See syntax and parameters in [BINDING_VALIDATING.md](BINDING_VALIDATING.md)

## Binding context

When an event associated with a hook is triggered, Shell-operator executes the hook without arguments. The information about the event that led to the hook execution is called the **binding context** and is written in JSON format to a temporary file. The path to this file is available to hook via environment variable `BINDING_CONTEXT_PATH`.
Expand Down
6 changes: 6 additions & 0 deletions examples/204-validating-webhook/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
templates
.helmignore
Chart.yaml
crontab*.yaml
README.md
values.yaml
6 changes: 6 additions & 0 deletions examples/204-validating-webhook/.helmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
crontab-valid.yaml
crontab-non-valid.yaml
Dockerfile
README.md
gen-certs.sh
shell-operator
4 changes: 4 additions & 0 deletions examples/204-validating-webhook/Chart.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
apiVersion: v1
name: 204-validating-webhook
version: 1.0.0
description: Shell-operator validating hook example
9 changes: 9 additions & 0 deletions examples/204-validating-webhook/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
FROM flant/shell-operator:latest
LABEL app="shell-operator" example="204-validating-webhook"

ADD frameworks/shell /frameworks/shell
# Add hooks
ADD hooks /hooks

# Compiled version of shell-operator binary
ADD shell-operator /
Loading