From ed8aa13cec213e34e960ef55e92a6e409fddd50d Mon Sep 17 00:00:00 2001 From: Sanskarzz Date: Mon, 3 Jun 2024 03:07:15 +0530 Subject: [PATCH 1/4] docs: added documentation of kyverno-envoy-plugin for website Signed-off-by: Sanskarzz --- quick_start.yaml | 215 +++++++++++ website/docs/index.md | 2 +- website/docs/intro.md | 38 ++ website/docs/jp/main.go | 51 +++ website/docs/performance.md | 0 website/docs/policies/asserts.md | 248 +++++++++++++ website/docs/policies/authz-policy.md | 123 +++++++ website/docs/policies/policies.md | 100 ++++++ website/docs/quick-start.md | 181 ++++++++++ website/docs/tutorials/istio.md | 343 ++++++++++++++++++ website/docs/tutorials/standalone-envoy.md | 393 +++++++++++++++++++++ website/mkdocs.yaml | 17 + 12 files changed, 1710 insertions(+), 1 deletion(-) create mode 100644 quick_start.yaml create mode 100644 website/docs/intro.md create mode 100644 website/docs/jp/main.go create mode 100644 website/docs/performance.md create mode 100644 website/docs/policies/asserts.md create mode 100644 website/docs/policies/authz-policy.md create mode 100644 website/docs/policies/policies.md create mode 100644 website/docs/quick-start.md create mode 100644 website/docs/tutorials/istio.md create mode 100644 website/docs/tutorials/standalone-envoy.md diff --git a/quick_start.yaml b/quick_start.yaml new file mode 100644 index 0000000..a826c88 --- /dev/null +++ b/quick_start.yaml @@ -0,0 +1,215 @@ +# Application Deployment with kyverno-envoy-plugin and Envoy sidecars. +apiVersion: apps/v1 +kind: Deployment +metadata: + name: testapp + namespace: demo +spec: + replicas: 1 + selector: + matchLabels: + app: testapp + template: + metadata: + labels: + app: testapp + spec: + initContainers: + - name: proxy-init + image: sanskardevops/proxyinit:latest + # Configure the iptables bootstrap script to redirect traffic to the + # Envoy proxy on port 8000, specify that Envoy will be running as user + # 1111, and that we want to exclude port 8181 from the proxy for the Kyverno health checks. + # These values must match up with the configuration + # defined below for the "envoy" and "kyverno-envoy-plugin" containers. + args: ["-p", "7000", "-u", "1111", -w, "8181"] + securityContext: + capabilities: + add: + - NET_ADMIN + runAsNonRoot: false + runAsUser: 0 + containers: + - name: test-application + image: sanskardevops/test-application:0.0.1 + ports: + - containerPort: 8080 + - name: envoy + image: envoyproxy/envoy:v1.30-latest + securityContext: + runAsUser: 1111 + imagePullPolicy: IfNotPresent + volumeMounts: + - readOnly: true + mountPath: /config + name: proxy-config + args: + - "envoy" + - "--config-path" + - "/config/envoy.yaml" + - name: kyverno-envoy-plugin + image: sanskardevops/plugin:0.0.34 + imagePullPolicy: IfNotPresent + ports: + - containerPort: 8181 + - containerPort: 9000 + volumeMounts: + - readOnly: true + mountPath: /policies + name: policy-files + args: + - "serve" + - "--policy=/policies/policy.yaml" + - "--address=:9000" + - "--healthaddress=:8181" + livenessProbe: + httpGet: + path: /health + scheme: HTTP + port: 8181 + initialDelaySeconds: 5 + periodSeconds: 5 + readinessProbe: + httpGet: + path: /health + scheme: HTTP + port: 8181 + initialDelaySeconds: 5 + periodSeconds: 5 + volumes: + - name: proxy-config + configMap: + name: proxy-config + - name: policy-files + configMap: + name: policy-files +--- +# Envoy Config with External Authorization filter that will query kyverno-envoy-plugin. +apiVersion: v1 +kind: ConfigMap +metadata: + name: proxy-config + namespace: demo +data: + envoy.yaml: | + static_resources: + listeners: + - address: + socket_address: + address: 0.0.0.0 + port_value: 7000 + filter_chains: + - filters: + - name: envoy.filters.network.http_connection_manager + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + codec_type: auto + stat_prefix: ingress_http + route_config: + name: local_route + virtual_hosts: + - name: backend + domains: + - "*" + routes: + - match: + prefix: "/" + route: + cluster: service + http_filters: + - name: envoy.ext_authz + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthz + transport_api_version: V3 + with_request_body: + max_request_bytes: 8192 + allow_partial_message: true + failure_mode_allow: false + grpc_service: + google_grpc: + target_uri: 127.0.0.1:9000 + stat_prefix: ext_authz + timeout: 0.5s + - name: envoy.filters.http.router + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router + clusters: + - name: service + connect_timeout: 0.25s + type: strict_dns + lb_policy: round_robin + load_assignment: + cluster_name: service + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: 127.0.0.1 + port_value: 8080 + admin: + access_log_path: "/dev/null" + address: + socket_address: + address: 0.0.0.0 + port_value: 8001 + layered_runtime: + layers: + - name: static_layer_0 + static_layer: + envoy: + resource_limits: + listener: + example_listener_name: + connection_limit: 10000 + overload: + global_downstream_max_connections: 50000 +--- +# Example policy to enforce into kyverno-envoy-plugin sidecars. +apiVersion: v1 +kind: ConfigMap +metadata: + name: policy-files + namespace: demo +data: + policy.yaml: | + apiVersion: json.kyverno.io/v1alpha1 + kind: ValidatingPolicy + metadata: + name: checkrequest + spec: + rules: + - name: deny-guest-request-at-post + assert: + any: + - message: "POST method calls at path /book are not allowed to guests users" + check: + request: + http: + method: POST + headers: + authorization: + (split(@, ' ')[1]): + (jwt_decode(@ , 'secret').payload.role): admin + path: /book + - message: "GET method call is allowed to both guest and admin users" + check: + request: + http: + method: GET + headers: + authorization: + (split(@, ' ')[1]): + (jwt_decode(@ , 'secret').payload.role): admin + path: /book + - message: "GET method call is allowed to both guest and admin users" + check: + request: + http: + method: GET + headers: + authorization: + (split(@, ' ')[1]): + (jwt_decode(@ , 'secret').payload.role): guest + path: /book + \ No newline at end of file diff --git a/website/docs/index.md b/website/docs/index.md index efdf7a6..57d0499 100644 --- a/website/docs/index.md +++ b/website/docs/index.md @@ -1,4 +1,4 @@ --- template: home.html -title: chainsaw +title: kyverno-envoy-plugin --- diff --git a/website/docs/intro.md b/website/docs/intro.md new file mode 100644 index 0000000..af41b78 --- /dev/null +++ b/website/docs/intro.md @@ -0,0 +1,38 @@ +# Introduction + +The Kyverno Envoy Plugin is a powerful tool that integrates the Kyverno-json policy engine with the Envoy proxy. It allows you to enforce Kyverno policies on incoming and outgoing traffic in a service mesh environment, providing an additional layer of security and control over your applications. + +## Overview + +[Envoy](https://www.envoyproxy.io/docs/envoy/latest/intro/what_is_envoy) is a Layer 7 proxy and communication bus tailored for large-scale, modern service-oriented architectures. Starting from version 1.7.0, Envoy includes an [External Authorization filter](https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/security/ext_authz_filter.html) that interfaces with an authorization service to determine the legitimacy of incoming requests. + +This functionality allows authorization decisions to be offloaded to an external service, which can access the request context. The request context includes details such as the origin and destination of the network activity, as well as specifics of the network request (e.g., HTTP request). This information enables the external service to make a well-informed decision regarding the authorization of the incoming request processed by Envoy. + +## What is Kyverno-Envoy-Plugin? + +[Kyverno-envoy](https://github.com/kyverno/kyverno-envoy-plugin) plugin extends [Kyverno-json](https://kyverno.github.io/kyverno-json/latest/) with a gRPC server that implements [Envoy External Authorization API](https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/security/ext_authz_filter.html). This allows you to enforce Kyverno policies on incoming and outgoing traffic in a service mesh environment, providing an additional layer of security and control over your applications. You can use this version of Kyverno to enforce fine-grained, context-aware access control policies with Envoy without modifying your microservice. + +## How does this work? + +In addition to the Envoy sidecar, your application pods will include a kyverno-envoy component, either as a sidecar or as a separate pod. This kyverno-envoy will be configured to communicate with the Kyverno-envoy-plugin gRPC server. When Envoy receives an API request intended for your microservice, it consults the Kyverno-envoy-plugin server to determine whether the request should be permitted. + +Performing policy evaluations locally with Envoy is advantageous, as it eliminates the need for an additional network hop for authorization checks, thus enhancing both performance and availability. + + + +!!! info + + The Kyverno-Envoy-Plugin is frequently deployed in Kubernetes environments as a sidecar container or as a separate pod. Additionally, it can be used in other environments as a standalone process running alongside Envoy. + +## Additional Resources + +See the following pages on [envoyproxy.io](https://www.envoyproxy.io/) for more information on external authorization: + +- [External Authorization](https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/security/ext_authz_filter.html) to learn about the External Authorization filter. +- [Network](https://www.envoyproxy.io/docs/envoy/latest/configuration/listeners/network_filters/ext_authz_filter#config-network-filters-ext-authz) and [HTTP](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/ext_authz_filter#config-http-filters-ext-authz) for details on configuring the External Authorization filter. + + + + + + diff --git a/website/docs/jp/main.go b/website/docs/jp/main.go new file mode 100644 index 0000000..02bbcf2 --- /dev/null +++ b/website/docs/jp/main.go @@ -0,0 +1,51 @@ +package main + +import ( + "fmt" + "strings" + + jpfunctions "github.com/jmespath-community/go-jmespath/pkg/functions" + "github.com/kyverno/kyverno-json/pkg/engine/template/functions" + kyvernofunctions "github.com/kyverno/kyverno-json/pkg/engine/template/kyverno" +) + +func main() { + fmt.Println("# Functions") + fmt.Println() + fmt.Println("## built-in functions") + fmt.Println() + printFunctions(jpfunctions.GetDefaultFunctions()...) + fmt.Println() + fmt.Println("## custom functions") + fmt.Println() + printFunctions(functions.GetFunctions()...) + fmt.Println() + fmt.Println("## kyverno functions") + fmt.Println() + printFunctions(kyvernofunctions.GetBareFunctions()...) + fmt.Println() +} + +func printFunctions(funcs ...jpfunctions.FunctionEntry) { + fmt.Println("| Name | Signature |") + fmt.Println("|---|---|") + for _, function := range funcs { + fmt.Println("|", function.Name, "|", "`"+strings.ReplaceAll(functionString(function), "|", `\|`)+"`", "|") + } +} + +func functionString(f jpfunctions.FunctionEntry) string { + if f.Name == "" { + return "" + } + var args []string + for _, a := range f.Arguments { + var aTypes []string + for _, t := range a.Types { + aTypes = append(aTypes, string(t)) + } + args = append(args, strings.Join(aTypes, "|")) + } + output := fmt.Sprintf("%s(%s)", f.Name, strings.Join(args, ", ")) + return output +} diff --git a/website/docs/performance.md b/website/docs/performance.md new file mode 100644 index 0000000..e69de29 diff --git a/website/docs/policies/asserts.md b/website/docs/policies/asserts.md new file mode 100644 index 0000000..72be0ee --- /dev/null +++ b/website/docs/policies/asserts.md @@ -0,0 +1,248 @@ +# Assertion trees + +Assertion trees can be used to apply complex and dynamic conditional checks using [JMESPath](../jp.md) expressions. + +## Assert + +An `assert` declaration contains an `any` or `all` list in which each entry contains a: + +* `check`: the assertion check +* `message`: an optional message + +A check can contain one or more JMESPath expressions. Expressions represent projections of selected data in the JSON *payload* and the result of this projection is passed to descendants for further analysis. + +All comparisons happen in the leaves of the assertion tree. + +**A simple example**: + +This policy checks that a pod does not use the default service account: + +```yaml +apiVersion: json.kyverno.io/v1alpha1 +kind: ValidatingPolicy +metadata: + name: assert-sample +spec: + rules: + - name: foo-bar + match: + all: + - apiVersion: v1 + kind: Pod + assert: + all: + - message: "serviceAccountName 'default' is not allowed" + check: + spec: + (serviceAccountName == 'default'): false +``` + +**A detailed example**: + +Given the input payload below: + +```yaml +foo: + baz: true + bar: 4 + bat: 6 +``` + +It is possible to write a validation rule like this: + +```yaml +apiVersion: json.kyverno.io/v1alpha1 +kind: ValidatingPolicy +metadata: + name: test +spec: + rules: + - name: foo-bar-4 + validate: + assert: + all: + - message: "..." + check: + # project field `foo` onto itself, the content of `foo` becomes the current object for descendants + foo: + + # evaluate expression `(bar > `3`)`, the boolean result becomes the current object for descendants + # the `true` leaf is compared with the current value `true` + (bar > `3`): true + + # evaluate expression `(!baz)`, the boolean result becomes the current object for descendants + # the leaf `false` is compared with the current value `false` + (!baz): false + + # evaluate expression `(bar + bat)`, the numeric result becomes the current object for descendants + # the leaf `10` is compared with the current value `10` + (bar + bat): 10 +``` + +## Iterating with Projection Modifiers + +Assertion tree expressions support modifiers to influence the way projected values are processed. + +The `~` modifier applies to arrays and maps, it mean the input array or map elements will be processed individually by descendants. + +When the `~` modifier is not used, descendants receive the whole array, not each individual element. + +Consider the following input document: + +```yaml +foo: + bar: + - 1 + - 2 + - 3 +``` + +The policy below does not use the `~` modifier and `foo.bar` array is compared against the expected array: + +```yaml +apiVersion: json.kyverno.io/v1alpha1 +kind: ValidatingPolicy +metadata: + name: test +spec: + rules: + - name: foo-bar + validate: + assert: + all: + - foo: + # the content of the `bar` field will be compared against `[1, 2, 3]` + bar: + - 1 + - 2 + - 3 +``` + +With the `~` modifier, we can apply descendant assertions to all elements in the array individually. +The policy below ensures that all elements in the input array are `< 5`: + +```yaml +apiVersion: json.kyverno.io/v1alpha1 +kind: ValidatingPolicy +metadata: + name: test +spec: + rules: + - name: foo-bar + validate: + assert: + all: + - foo: + # with the `~` modifier all elements in the `[1, 2, 3]` array are processed individually and passed to descendants + ~.bar: + # the expression `(@ < `5`)` is evaluated for every element and the result is expected to be `true` + (@ < `5`): true +``` + +The `~` modifier supports binding the index of the element being processed to a named binding with the following syntax `~index_name.bar`. When this is used, we can access the element index in descendants with `$index_name`. + +When used with a map, the named binding receives the key of the element being processed. + +## Explicit bindings + +Sometimes it can be useful to refer to a parent node in the assertion tree. + +This is possible to add an explicit binding at every node in the tree by appending the `->binding_name` to the key. + +Given the input document: + +```yaml +foo: + bar: 4 + bat: 6 +``` + +The following policy will compute a sum and bind the result to the `sum` binding. A descendant can then use `$sum` and use it: + +```yaml +apiVersion: json.kyverno.io/v1alpha1 +kind: ValidatingPolicy +metadata: + name: test +spec: + rules: + - name: foo-bar + validate: + assert: + all: + - foo: + # evaluate expression `(bar + bat)` and bind it to `sum` + (bar + bat)->sum: + # get the `$sum` binding and compare it against `10` + ($sum): 10 +``` + +All binding are available to descendants, if a descendant creates a binding with a name that already exists the binding will be overridden for descendants only and it doesn't affect the bindings at upper levels in the tree. + +In other words, a node in the tree always sees bindings that are defined in the parents and if a name is reused, the first binding with the given name wins when winding up the tree. + +As a consequence, the policy below will evaluate to true: + +```yaml +apiVersion: json.kyverno.io/v1alpha1 +kind: ValidatingPolicy +metadata: + name: test +spec: + rules: + - name: foo-bar + validate: + assert: + all: + - foo: + (bar + bat)->sum: + ($sum + $sum)->sum: + ($sum): 20 + ($sum): 10 +``` + +Finally, we can always access the current payload, policy and rule being evaluated using the built-in `$payload`, `$policy` and `$rule` bindings. No protection is made to prevent you from overriding those bindings though. + + +## Escaping projection + +It can be necessary to prevent a projection under certain circumstances. + +Consider the following document: + +```yaml +foo: + (bar): 4 + (baz): + - 1 + - 2 + - 3 +``` + +Here the `(bar)` key conflict with the projection syntax. +To workaround this situation, you can escape a projection by surrounding it with `\` characters like this: + +```yaml +apiVersion: json.kyverno.io/v1alpha1 +kind: ValidatingPolicy +metadata: + name: test +spec: + rules: + - name: foo-bar + validate: + assert: + all: + - foo: + \(bar)\: 10 +``` + +In this case, the leading and trailing `\` characters will be erased and the projection won't be applied. + +Note that it's still possible to use the `~` modifier or to create a named binding with and escaped projection. + +Keys like this are perfectly valid: + +- `~index.\baz\` +- `\baz\@foo` +- `~index.\baz\@foo` diff --git a/website/docs/policies/authz-policy.md b/website/docs/policies/authz-policy.md new file mode 100644 index 0000000..5013244 --- /dev/null +++ b/website/docs/policies/authz-policy.md @@ -0,0 +1,123 @@ +# Policy Reference + +This page provides guidance on writing policies for request content processed by the kyverno-json validating policy, utilizing Envoy’s [External Authorization filter](https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/security/ext_authz_filter.html). + +### Writing Policies + +Let start with an example policy that restricts access to an endpoint based on user's role and permissions. + +```yaml +apiVersion: json.kyverno.io/v1alpha1 +kind: ValidatingPolicy +metadata: + name: checkrequest +spec: + rules: + - name: deny-guest-request-at-post + assert: + any: + - message: "POST method calls at path /book are not allowed to guests users" + check: + request: + http: + method: POST + headers: + authorization: + (split(@, ' ')[1]): + (jwt_decode(@ , 'secret').payload.role): admin + path: /book + - message: "GET method call is allowed to both guest and admin users" + check: + request: + http: + method: GET + headers: + authorization: + (split(@, ' ')[1]): + (jwt_decode(@ , 'secret').payload.role): admin + path: /book + - message: "GET method call is allowed to both guest and admin users" + check: + request: + http: + method: GET + headers: + authorization: + (split(@, ' ')[1]): + (jwt_decode(@ , 'secret').payload.role): guest + path: /book +``` + +The above policy uses the `jwt_decode` builtin function to parse and verify the JWT containing information about the user making the request. it uses other builtins like `split`, `base64_decode`, `campare`, `contains` etc kyverno has many different [function](https://kyverno.github.io/kyverno-json/latest/jp/functions/) which can be used in policy. + +Sample input recevied by kyverno-json validating policy is shown below: + +```json +{ + "source": { + "address": { + "socketAddress": { + "address": "10.244.1.10", + "portValue": 59252 + } + } + }, + "destination": { + "address": { + "socketAddress": { + "address": "10.244.1.4", + "portValue": 8080 + } + } + }, + "request": { + "time": "2024-04-09T07:42:29.634453Z", + "http": { + "id": "14694995155993896575", + "method": "GET", + "headers": { + ":authority": "testapp.demo.svc.cluster.local:8080", + ":method": "GET", + ":path": "/book", + ":scheme": "http", + "authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjIyNDEwODE1MzksIm5iZiI6MTUxNDg1MTEzOSwicm9sZSI6Imd1ZXN0Iiwic3ViIjoiWVd4cFkyVT0ifQ.ja1bgvIt47393ba_WbSBm35NrUhdxM4mOVQN8iXz8lk", + "user-agent": "Wget", + "x-forwarded-proto": "http", + "x-request-id": "27cd2724-e0f4-4a69-a1b1-9a94edfa31bb" + }, + "path": "/book", + "host": "echo.demo.svc.cluster.local:8080", + "scheme": "http", + "protocol": "HTTP/1.1" + } + }, + "metadataContext": {}, + "routeMetadataContext": {} +} +``` + +With the help of assertion tree, we can write policies that can be used to validate the request content. + +An `assert` declaration contains an `any` or `all` list in which each entry contains a `check` and a `message`. The `check` contains a JMESPath expression that is evaluated against the request content. The `message` is a string that is returned when the check fails. +A check can contain one or more JMESPath expressions. Expressions represent projections of seleted data in the JSON payload and the result of this projection is passed to descendants for futher analysis. All comparisons happen in the leaves of the assertion tree. + +For more detail checkout [Policy Structure](https://kyverno.github.io/kyverno-json/latest/policies/policies/) and [Assertion trees](https://kyverno.github.io/kyverno-json/latest/policies/asserts/#assertion-trees). + +- HTTP method `request.http.method` +- Request path `request.http.path` +- Authorization header `request.http.headers.authorization` + +when we decode this above mentioned JWT token in the request payload we get payload.role `guest`: + +```json +{ + "exp": 2241081539, + "nbf": 1514851139, + "role": "guest", + "sub": "YWxpY2U=" +} +``` +With the input value above, the answer is: +``` +true +``` diff --git a/website/docs/policies/policies.md b/website/docs/policies/policies.md new file mode 100644 index 0000000..8eccf1e --- /dev/null +++ b/website/docs/policies/policies.md @@ -0,0 +1,100 @@ +# Policy Structure + +Kyverno policies are [Kubernetes resources](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/) and can be easily managed via Kubernetes APIs, GitOps workflows, and other existing tools. + +Policies that apply to JSON payload have a few differences from Kyverno policies that are applied to Kubernetes resources at admission controls. + +## Resource Scope + +Policies that apply to JSON payloads are always cluster-wide resources. + +## API Group and Kind + +`kyverno-json` policies belong to the `json.kyverno.io` group and can only be of kind `ValidatingPolicy`. + +```yaml +apiVersion: json.kyverno.io/v1alpha1 +kind: ValidatingPolicy +metadata: + name: test +spec: + rules: + - name: foo-bar-4 + validate: + assert: + all: + - foo: + bar: 4 +``` + +## Policy Rules + +A policy can have multiple rules, and rules are processed in order. Evaluation stops at the first rule that fails. + +## Match and Exclude + +Policies that apply to JSON payloads use [assertion trees](./asserts.md) in both the `match`/`exclude` declarations as well as the `validate` rule declaration. + +```yaml +apiVersion: json.kyverno.io/v1alpha1 +kind: ValidatingPolicy +metadata: + name: required-s3-tags +spec: + rules: + - name: require-team-tag + identifier: address + match: + any: + - type: aws_s3_bucket + exclude: + any: + - name: bypass-me + validate: + assert: + all: + - values: + tags: + Team: ?* +``` + +In the example above, every *resource* having `type: aws_s3_bucket` will match, and *payloads* having `name: bypass-me` will be excluded. + +## Identifying Payload Entries + +A policy rule can contain an optional `identifier` which declares the path to the payload element that uniquely identifies each entry. + +## Context Entries + +A policy rule can contain optional `context` entries that are made available to the rule via bindings: + +```yaml +apiVersion: json.kyverno.io/v1alpha1 +kind: ValidatingPolicy +metadata: + name: required-s3-tags +spec: + rules: + - name: require-team-tag + match: + any: + - type: aws_s3_bucket + context: + # creates a `expectedTeam` binding automatically + - name: expectedTeam + variable: Kyverno + validate: + message: Bucket `{{ name }}` does not have the required Team tag {{ $expectedTeam }} + assert: + all: + - values: + tags: + # use the `$expectedTeam` binding coming from the context + Team: ($expectedTeam) +``` + +## No `forEach`, `pattern operators`, `anchors`, or `wildcards` + +The use of [assertion trees](./asserts.md) addresses some features of Kyverno policies that apply to Kubernetes resources. + +Specifically, [forEach](https://kyverno.io/docs/writing-policies/validate/#foreach), [pattern operators](https://kyverno.io/docs/writing-policies/validate/#operators), [anchors](https://kyverno.io/docs/writing-policies/validate/#anchors), or [wildcards](https://kyverno.io/docs/writing-policies/validate/#wildcards) are not supported for policies that apply to JSON resources. Instead, [assertion trees](./asserts.md) with [JMESPath](../jp.md) expressions are used to achieve the same powerful features. \ No newline at end of file diff --git a/website/docs/quick-start.md b/website/docs/quick-start.md new file mode 100644 index 0000000..995bac2 --- /dev/null +++ b/website/docs/quick-start.md @@ -0,0 +1,181 @@ +# Quick Start + +This section presumes testing is conducted with Envoy version 1.10.0 or newer. + +### Required tools + +1. [`minikube`](https://minikube.sigs.k8s.io/docs/) +1. [`kubectl`](https://kubernetes.io/docs/tasks/tools/install-kubectl/) + +### Create a local cluster + +Start minikube cluster with the following command: + +```console +minikube start +``` +### Install kyverno-envoy sidecar with application + +Install application with envoy and kyverno-envoy-plugin as a sidecar container. + +```console +kubectl apply -f https://raw.githubusercontent.com/kyverno/kyverno-envoy-plugin/main/quick_start.yaml +``` +The `applicaition.yaml` manifest defines the following resource: + +- The Deployment includes an example Go application that provides information of books in the library books collection and exposes APIs to `get`, `create` and `delete` books collection. Check this out for more information about the [Go test application](https://github.com/Sanskarzz/kyverno-envoy-demos/tree/main/test-application) . + +- The Deployment also includes a kyverno-envoy-plugin sidecar container in addition to the Envoy sidecar container. When Envoy recevies API request destined for the Go test applicaiton, it will check with kyverno-envoy-plugin to decide if the request should be allowed and the kyverno-envoy-plugin sidecar container is configured to query Kyverno-json engine for policy decisions on incoming requests. + +- A ConfigMap `policy-config` is used to pass the policy to kyverno-envoy-plugin sidecar in the namespace `default` where the application is deployed . + +- A ConfigMap `envoy-config` is used to pass an Envoy configuration with an External Authorization Filter to direct authorization checks to the kyverno-envoy-plugin sidecar. + +- The Deployment also includes an init container that install iptables rules to redirect all container traffic to the Envoy proxy sidecar container , more about init container can be found [here](./envoy_iptables) + +### Make Test application accessible in the cluster . + +```console +kubectl expose deployment testapp --type=NodePort --name=testapp-service --port=8080 +``` + +### Set the `SERVICE_URL` environment variable to the service's IP/port. + +minikube: + +```sh +export SERVICE_PORT=$(kubectl get service testapp -o jsonpath='{.spec.ports[?(@.port==8080)].nodePort}') +export SERVICE_HOST=$(minikube ip) +export SERVICE_URL=$SERVICE_HOST:$SERVICE_PORT +echo $SERVICE_URL +``` +### Calling the sample test application and verify the authorization + +For convenience, we’ll want to store Alice’s and Bob’s tokens in environment variables. Here bob is assigned the admin role and alice is assigned the guest role. + +```bash +export ALICE_TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjIyNDEwODE1MzksIm5iZiI6MTUxNDg1MTEzOSwicm9sZSI6Imd1ZXN0Iiwic3ViIjoiWVd4cFkyVT0ifQ.ja1bgvIt47393ba_WbSBm35NrUhdxM4mOVQN8iXz8lk" +export BOB_TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjIyNDEwODE1MzksIm5iZiI6MTUxNDg1MTEzOSwicm9sZSI6ImFkbWluIiwic3ViIjoiWVd4cFkyVT0ifQ.veMeVDYlulTdieeX-jxFZ_tCmqQ_K8rwx2OktUHv5Z0" +``` + +The policy we passed to kyverno-envoy-plugin sidecar in the ConfigMap `policy-config` is configured to check the conditions of the incoming request and denies the request if the user is a guest and the request method is `POST` at the `/book` path. + +```yaml +apiVersion: json.kyverno.io/v1alpha1 +kind: ValidatingPolicy +metadata: + name: checkrequest +spec: + rules: + - name: deny-guest-request-at-post + assert: + any: + - message: "POST method calls at path /book are not allowed to guests users" + check: + request: + http: + method: POST + headers: + authorization: + (split(@, ' ')[1]): + (jwt_decode(@ , 'secret').payload.role): admin + path: /book + - message: "GET method call is allowed to both guest and admin users" + check: + request: + http: + method: GET + headers: + authorization: + (split(@, ' ')[1]): + (jwt_decode(@ , 'secret').payload.role): admin + path: /book + - message: "GET method call is allowed to both guest and admin users" + check: + request: + http: + method: GET + headers: + authorization: + (split(@, ' ')[1]): + (jwt_decode(@ , 'secret').payload.role): guest + path: /book +``` + +Check for `Alice` which can get book but cannot create book. + +```bash +curl -i -H "Authorization: Bearer "$ALICE_TOKEN"" http://$SERVICE_URL/book +``` +```bash +curl -i -H "Authorization: Bearer "$ALICE_TOKEN"" -d '{"bookname":"Harry Potter", "author":"J.K. Rowling"}' -H "Content-Type: application/json" -X POST http://$SERVICE_URL/book +``` +Check the `Bob` which can get book also create the book + +```bash +curl -i -H "Authorization: Bearer "$BOB_TOKEN"" http://$SERVICE_URL/book +``` + +```bash +curl -i -H "Authorization: Bearer "$BOB_TOKEN"" -d '{"bookname":"Harry Potter", "author":"J.K. Rowling"}' -H "Content-Type: application/json" -X POST http://$SERVICE_URL/book +``` + +Check on logs +```bash +kubectl logs "$(kubectl get pod -l app=testapp -o jsonpath={.items..metadata.name})" -c kyverno-envoy-plugin -f +``` +First , third and last request is passed but second request is failed. + +```console +sanskar@sanskar-HP-Laptop-15s-du1xxx:~$ kubectl logs "$(kubectl get pod -l app=testapp -n demo -o jsonpath={.items..metadata.name})" -n demo -c kyverno-envoy-plugin -f +Starting HTTP server on Port 8000 +Starting GRPC server on Port 9000 +Request is initialized in kyvernojson engine . +2024/04/26 17:11:42 Request passed the deny-guest-request-at-post policy rule. +Request is initialized in kyvernojson engine . +2024/04/26 17:22:11 Request violation: -> POST method calls at path /book are not allowed to guests users + -> any[0].check.request.http.headers.authorization.(split(@, ' ')[1]).(jwt_decode(@ , 'secret').payload.role): Invalid value: "guest": Expected value: "admin" +-> GET method call is allowed to both guest and admin users + -> any[1].check.request.http.headers.authorization.(split(@, ' ')[1]).(jwt_decode(@ , 'secret').payload.role): Invalid value: "guest": Expected value: "admin" + -> any[1].check.request.http.method: Invalid value: "POST": Expected value: "GET" +-> GET method call is allowed to both guest and admin users + -> any[2].check.request.http.method: Invalid value: "POST": Expected value: "GET" +Request is initialized in kyvernojson engine . +2024/04/26 17:23:13 Request passed the deny-guest-request-at-post policy rule. +Request is initialized in kyvernojson engine . +2024/04/26 17:23:55 Request passed the deny-guest-request-at-post policy rule. +``` + +### Configuration + +To deploy Kyverno-Envoy include the following container in your kubernetes Deployments: + +```yaml +- name: kyverno-envoy-plugin + image: sanskardevops/plugin:0.0.34 + imagePullPolicy: IfNotPresent + ports: + - containerPort: 8181 + - containerPort: 9000 + volumeMounts: + - readOnly: true + args: + - "serve" + - "--policy=/policies/policy.yaml" + - "--address=:9000" + - "--healthaddress=:8181" + livenessProbe: + httpGet: + path: /health + scheme: HTTP + port: 8181 + initialDelaySeconds: 5 + periodSeconds: 5 + readinessProbe: + httpGet: + path: /health + scheme: HTTP + port: 8181 + initialDelaySeconds: 5 + periodSeconds: 5 +``` \ No newline at end of file diff --git a/website/docs/tutorials/istio.md b/website/docs/tutorials/istio.md new file mode 100644 index 0000000..633d505 --- /dev/null +++ b/website/docs/tutorials/istio.md @@ -0,0 +1,343 @@ +# Istio + +[Istio](https://istio.io/latest/) is an open source service mesh for managing the different microservices that make up a cloud-native application. Istio provides a mechanism to use a service as an external authorizer with the [AuthorizationPolicy API](https://istio.io/latest/docs/tasks/security/authorization/authz-custom/). + +This tutorial shows how Istio’s AuthorizationPolicy can be configured to delegate authorization decisions to Kyverno-envoy-plugin. + +## Prerequisites + +This tutorial requires Kubernetes 1.20 or later. To run the tutorial locally ensure you start a cluster with Kubernetes version 1.20+, we recommend using [minikube](https://kubernetes.io/docs/getting-started-guides/minikube) or [KIND](https://kind.sigs.k8s.io/). + +The tutorial also requries istio v1.19.0 or later. To install istio, follow the instructions [here](https://istio.io/latest/docs/setup/getting-started/) or run the below script it will create a kind cluster and install istio + +```sh +#!/bin/bash + +KIND_IMAGE=kindest/node:v1.29.2 +ISTIO_REPO=https://istio-release.storage.googleapis.com/charts +ISTIO_NS=istio-system + +# Create Kind cluster +kind create cluster --image $KIND_IMAGE --wait 1m --config - < 8080/TCP 4h5m + +NAME READY UP-TO-DATE AVAILABLE AGE +deployment.apps/echo 1/1 1 1 3h59m + +NAME DESIRED CURRENT READY AGE +replicaset.apps/echo-55c77757f4 1 1 1 3h59m +``` + +### ServiceEntry + +ServiceEntry to registor the kyverno-envoy-plugin sidecar as external authorizer and ServiceEntry to allow Istio to find the Kyverno-Envoy-Plugin sidecar. + +```console +kubectl apply -f ./manifests/service-entry.yaml +``` +```yaml +apiVersion: networking.istio.io/v1beta1 +kind: ServiceEntry +metadata: + name: kyverno-ext-authz-grpc-local +spec: + hosts: + - "kyverno-ext-authz-grpc.local" + # The service name to be used in the extension provider in the mesh config. + endpoints: + - address: "127.0.0.1" + ports: + - name: grpc + number: 9000 + # The port number to be used in the extension provider in the mesh config. + protocol: GRPC + resolution: STATIC +``` +### Register authorization provider + +Edit the mesh configmap to register authorization provider with the following command: + +```console +kubectl edit configmap istio -n istio-system +``` + +In the editor, add the extension provider definitions to the mesh configmap. + +```yaml + data: + mesh: |- + extensionProviders: + - name: "kyverno-ext-authz-grpc" + envoyExtAuthzGrpc: + service: "kyverno-ext-authz-grpc.local" + port: "9000" +``` + +### Authorization policy + +AuthorizationPolicy to direct authorization checks to the Kyverno-Envoy-Plugin sidecar. + +```shell +$ kubectl apply -f - <}} +If you haven't used `kind` before, you can find installation instructions +in the [project documentation](https://kind.sigs.k8s.io/#installation-and-usage). +{{}} + +### Running a local Kubernetes cluster + +To start a local kubernetes cluster to run our demo, we'll be using [kind](https://kind.sigs.k8s.io/). In order to use the kind command, you’ll need to have Docker installed on your machine. + +Create a cluster with the following command: + +```shell +$ kind create cluster --name kyverno-tutorial --image kindest/node:v1.29.2 +Creating cluster "kyverno-tutorial" ... + ✓ Ensuring node image (kindest/node:v1.29.2) 🖼 + ✓ Preparing nodes 📦 + ✓ Writing configuration 📜 + ✓ Starting control-plane 🕹️ + ✓ Installing CNI 🔌 + ✓ Installing StorageClass 💾 +Set kubectl context to "kind-kyverno-tutorial" +You can now use your cluster with: + +kubectl cluster-info --context kind-kyverno-tutorial + +Thanks for using kind! 😊 +``` + +Listing the cluster nodes, should show something like this: + +```shell +$ kubectl get nodes +NAME STATUS ROLES AGE VERSION +kyverno-tutorial-control-plane Ready control-plane 79s v1.29.2 +``` + +### Creating a simple authorization policy + +This tutorial assumes you have some basic knowledge of [validatingPolicy](https://kyverno.github.io/kyverno-json/latest/policies/policies/#policy-structure) and [assertion trees](https://kyverno.github.io/kyverno-json/latest/policies/asserts/). In summary the policy below does the following: + +- Checks that the JWT token is valid +- Checks that the action is allowed based on the token payload `role` and the request path +- Guests have read-only access to the `/book` endpoint, admins can create users too as long as the name is not the same as the admin's name. + +```yaml +apiVersion: json.kyverno.io/v1alpha1 +kind: ValidatingPolicy +metadata: + name: checkrequest +spec: + rules: + - name: deny-guest-request-at-post + assert: + any: + - message: "POST method calls at path /book are not allowed to guests users" + check: + request: + http: + method: POST + headers: + authorization: + (split(@, ' ')[1]): + (jwt_decode(@ , 'secret').payload.role): admin + path: /book + - message: "GET method call is allowed to both guest and admin users" + check: + request: + http: + method: GET + headers: + authorization: + (split(@, ' ')[1]): + (jwt_decode(@ , 'secret').payload.role): admin + path: /book + - message: "GET method call is allowed to both guest and admin users" + check: + request: + http: + method: GET + headers: + authorization: + (split(@, ' ')[1]): + (jwt_decode(@ , 'secret').payload.role): guest + path: /book +``` + +Create a file called policy.yaml with the above content and store it in a configMap: + +```shell +$ kubectl create configmap policy --from-file=policy.yaml +``` + +### Deploying an application with Envoy and Kyverno-Envoy-Plugin sidecars + +In this tutorial, we are manually configuring the Envoy proxy sidecar to intermediate HTTP traffic from clients and our application. Envoy will consult Kyverno-Envoy-Plugin to make authorization decisions for each request by sending `CheckRequest` gRPC messages over a gRPC connection. + +We will use the following Envoy configuration to achieve this. In summary, this configures Envoy to: + +- Listen on Port `7000` for HTTP traffic +- Consult Kyverno-Envoy-Plugin at `127.0.0.1:9000` for authorization decisions and deny failing requests +- Forward request to the application at `127.0.0.1:8080` if ok. + +```yaml + static_resources: + listeners: + - address: + socket_address: + address: 0.0.0.0 + port_value: 7000 + filter_chains: + - filters: + - name: envoy.filters.network.http_connection_manager + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + codec_type: auto + stat_prefix: ingress_http + route_config: + name: local_route + virtual_hosts: + - name: backend + domains: + - "*" + routes: + - match: + prefix: "/" + route: + cluster: service + http_filters: + - name: envoy.ext_authz + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthz + transport_api_version: V3 + with_request_body: + max_request_bytes: 8192 + allow_partial_message: true + failure_mode_allow: false + grpc_service: + google_grpc: + target_uri: 127.0.0.1:9000 + stat_prefix: ext_authz + timeout: 0.5s + - name: envoy.filters.http.router + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router + clusters: + - name: service + connect_timeout: 0.25s + type: strict_dns + lb_policy: round_robin + load_assignment: + cluster_name: service + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: 127.0.0.1 + port_value: 8080 + admin: + access_log_path: "/dev/null" + address: + socket_address: + address: 0.0.0.0 + port_value: 8001 + layered_runtime: + layers: + - name: static_layer_0 + static_layer: + envoy: + resource_limits: + listener: + example_listener_name: + connection_limit: 10000 + overload: + global_downstream_max_connections: 50000 +``` + +Create a `ConfigMap` containing the above configuration by running: + +```shell +$ kubectl create configmap proxy-config --from-file envoy.yaml +``` +Our application will be configured using a `Deployment` and `Service`. There are few things to note: + +- The pods have an `initContainer` that configures the `iptables` rules to redirect traffic to the Envoy Proxy sidecar. +- The `test-application` container is simple go application stores book information in-memory state. +- The `envoy` container is configured to use `proxy-config` `ConfigMap` as the Envoy configuration we created earlier +- The `kyverno-envoy-plugin` container is configured to use `policy` `ConfigMap` as the Kyverno policy we created earlier + +```yaml +# test-application.yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: testapp + namespace: demo +spec: + replicas: 1 + selector: + matchLabels: + app: testapp + template: + metadata: + labels: + app: testapp + spec: + initContainers: + - name: proxy-init + image: sanskardevops/proxyinit:latest + # Configure the iptables bootstrap script to redirect traffic to the + # Envoy proxy on port 8000, specify that Envoy will be running as user + # 1111, and that we want to exclude port 8181 from the proxy for the Kyverno health checks. + # These values must match up with the configuration + # defined below for the "envoy" and "kyverno-envoy-plugin" containers. + args: ["-p", "7000", "-u", "1111", -w, "8181"] + securityContext: + capabilities: + add: + - NET_ADMIN + runAsNonRoot: false + runAsUser: 0 + containers: + - name: test-application + image: sanskardevops/test-application:0.0.1 + ports: + - containerPort: 8080 + - name: envoy + image: envoyproxy/envoy:v1.30-latest + securityContext: + runAsUser: 1111 + imagePullPolicy: IfNotPresent + volumeMounts: + - readOnly: true + mountPath: /config + name: proxy-config + args: + - "envoy" + - "--config-path" + - "/config/envoy.yaml" + - name: kyverno-envoy-plugin + image: sanskardevops/plugin:0.0.34 + imagePullPolicy: IfNotPresent + ports: + - containerPort: 8181 + - containerPort: 9000 + volumeMounts: + - readOnly: true + mountPath: /policies + name: policy-files + args: + - "serve" + - "--policy=/policies/policy.yaml" + - "--address=:9000" + - "--healthaddress=:8181" + livenessProbe: + httpGet: + path: /health + scheme: HTTP + port: 8181 + initialDelaySeconds: 5 + periodSeconds: 5 + readinessProbe: + httpGet: + path: /health + scheme: HTTP + port: 8181 + initialDelaySeconds: 5 + periodSeconds: 5 + volumes: + - name: proxy-config + configMap: + name: proxy-config + - name: policy-files + configMap: + name: policy-files +--- +apiVersion: v1 +kind: Service +metadata: + name: testapp + namespace: demo +spec: + type: ClusterIP + selector: + app: testapp + ports: + - port: 8080 + targetPort: 8080 +``` + +Deploy the application and Kubernetes Service to the cluster with: + +```shell +$ kubectl apply -f test-application.yaml +``` +Check that everything is working by listing the pod and make sure all three pods are running ok. + +```shell +$ kubectl get pods +NAME READY STATUS RESTARTS AGE +testapp-74b4bc88-5d4wh 3/3 Running 0 1m +``` +### Policy in action + +For convenience, we’ll want to store Alice’s and Bob’s tokens in environment variables. Here bob is assigned the admin role and alice is assigned the guest role. + +```bash +export ALICE_TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjIyNDEwODE1MzksIm5iZiI6MTUxNDg1MTEzOSwicm9sZSI6Imd1ZXN0Iiwic3ViIjoiWVd4cFkyVT0ifQ.ja1bgvIt47393ba_WbSBm35NrUhdxM4mOVQN8iXz8lk" +export BOB_TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjIyNDEwODE1MzksIm5iZiI6MTUxNDg1MTEzOSwicm9sZSI6ImFkbWluIiwic3ViIjoiWVd4cFkyVT0ifQ.veMeVDYlulTdieeX-jxFZ_tCmqQ_K8rwx2OktUHv5Z0" +``` + +Check for `Alice` which can get book but cannot create book. + +```bash +kubectl run test -it --rm --restart=Never --image=busybox -- wget -q --header="authorization: Bearer "$ALICE_TOKEN"" --output-document - testapp.demo.svc.cluster.local:8080/book +``` +```bash +kubectl run test -it --rm --restart=Never --image=busybox -- wget -q --header="authorization: Bearer "$ALICE_TOKEN"" --post-data='{"bookname":"Harry Potter", "author":"J.K. Rowling"}' --output-document - testapp.demo.svc.cluster.local:8080/book +``` +Check the `Bob` which can get book also create the book + +```bash +kubectl run test -it --rm --restart=Never --image=busybox -- wget -q --header="authorization: Bearer "$BOB_TOKEN"" --output-document - testapp.demo.svc.cluster.local:8080/book +``` + +```bash +kubectl run test -it --rm --restart=Never --image=busybox -- wget -q --header="authorization: Bearer "$BOB_TOKEN"" --post-data='{"bookname":"Harry Potter", "author":"J.K. Rowling"}' --output-document - testapp.demo.svc.cluster.local:8080/book +``` + +Check on logs +```bash +kubectl logs "$(kubectl get pod -l app=testapp -n demo -o jsonpath={.items..metadata.name})" -n demo -c kyverno-envoy-plugin -f +``` +First , third and last request is passed but second request is failed. + +```console +sanskar@sanskar-HP-Laptop-15s-du1xxx:~$ kubectl logs "$(kubectl get pod -l app=testapp -n demo -o jsonpath={.items..metadata.name})" -n demo -c kyverno-envoy-plugin -f +Starting HTTP server on Port 8000 +Starting GRPC server on Port 9000 +Request is initialized in kyvernojson engine . +2024/04/26 17:11:42 Request passed the deny-guest-request-at-post policy rule. +Request is initialized in kyvernojson engine . +2024/04/26 17:22:11 Request violation: -> POST method calls at path /book are not allowed to guests users + -> any[0].check.request.http.headers.authorization.(split(@, ' ')[1]).(jwt_decode(@ , 'secret').payload.role): Invalid value: "guest": Expected value: "admin" +-> GET method call is allowed to both guest and admin users + -> any[1].check.request.http.headers.authorization.(split(@, ' ')[1]).(jwt_decode(@ , 'secret').payload.role): Invalid value: "guest": Expected value: "admin" + -> any[1].check.request.http.method: Invalid value: "POST": Expected value: "GET" +-> GET method call is allowed to both guest and admin users + -> any[2].check.request.http.method: Invalid value: "POST": Expected value: "GET" +Request is initialized in kyvernojson engine . +2024/04/26 17:23:13 Request passed the deny-guest-request-at-post policy rule. +Request is initialized in kyvernojson engine . +2024/04/26 17:23:55 Request passed the deny-guest-request-at-post policy rule. +``` + +### Cleanup + +Delete the cluster by running: +```shell +$ kind delete cluster --name kyverno-tutorial +``` +## Wrap Up +Congratulations on completing the tutorial! + +In this tutorial, you learned how to utilize the kyverno-envoy-plugin as an external authorization service to enforce custom policies through Envoy’s external authorization filter. + +The tutorial also included an example policy using kyverno-envoy-plugin that returns a boolean decision indicating whether a request should be permitted. + +Moreover, Envoy’s external authorization filter supports the inclusion of optional response headers and body content that can be sent to either the downstream client or upstream server. An example of a rule that not only determines request authorization but also provides optional response headers, body content, and HTTP status is available here. \ No newline at end of file diff --git a/website/mkdocs.yaml b/website/mkdocs.yaml index 89ca4b3..551008a 100644 --- a/website/mkdocs.yaml +++ b/website/mkdocs.yaml @@ -2,3 +2,20 @@ INHERIT: ./mkdocs.base.yaml nav: - Home: index.md +- Documentation: + - intro.md + - quickstart.md + - Writing policies: + - policies/policies.md + - policies/asserts.md + - policies/authz-policy.md + - JMESPath: + - Overview: jp.md + - Functions: jp/functions.md +- Tutorials: + - tutorials/standalone-envoy.md + - tutorials/istio.md + - tutorials/mtls-istio.md +- Performance: performance.md + + From c1074012bbc42b5c0355f03fa8ed35b9b1c5e589 Mon Sep 17 00:00:00 2001 From: Sanskarzz Date: Sun, 9 Jun 2024 18:19:15 +0530 Subject: [PATCH 2/4] added performance documentation Signed-off-by: Sanskarzz --- website/docs/performance.md | 427 ++++++++++++++++++++++++++++++++++++ 1 file changed, 427 insertions(+) diff --git a/website/docs/performance.md b/website/docs/performance.md index e69de29..8109e05 100644 --- a/website/docs/performance.md +++ b/website/docs/performance.md @@ -0,0 +1,427 @@ +# Performance + +This page offers guidance and best practices for benchmarking the performance of the kyverno-envoy-plugin, helping users understand the associated overhead. It outlines an example setup for conducting benchmarks, various benchmarking scenarios, and key metrics to capture for assessing the impact of the kyverno-envoy-plugin. + +## Benchmark Setup + +The benchmark setup consists of the following components: + +### Sample Application + +The first component is a simple Go application that provides information of books in the library books collection and exposes APIs to `get`, `create` and `delete` books collection. Check this out for more information about the [Go test application](https://github.com/Sanskarzz/kyverno-envoy-demos/tree/main/test-application) . + +### Envoy + +The second component is the Envoy proxy, which runs alongside the example application. The Envoy configuration defines an external authorization filter `envoy.ext_authz` for a gRPC authorization server. The config uses Envoy's in-built gRPC client to make external gRPC calls. + +``` +static_resources: + listeners: + - address: + socket_address: + address: 0.0.0.0 + port_value: 8000 + filter_chains: + - filters: + - name: envoy.filters.network.http_connection_manager + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + codec_type: auto + stat_prefix: ingress_http + route_config: + name: local_route + virtual_hosts: + - name: backend + domains: + - "*" + routes: + - match: + prefix: "/" + route: + cluster: service + http_filters: + - name: envoy.ext_authz + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthz + transport_api_version: V3 + with_request_body: + max_request_bytes: 8192 + allow_partial_message: true + failure_mode_allow: false + grpc_service: + google_grpc: + target_uri: 127.0.0.1:9191 + stat_prefix: ext_authz + timeout: 0.5s + - name: envoy.filters.http.router + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router + clusters: + - name: service + connect_timeout: 0.25s + type: strict_dns + lb_policy: round_robin + load_assignment: + cluster_name: service + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: 127.0.0.1 + port_value: 8080 +admin: + access_log_path: "/dev/null" + address: + socket_address: + address: 0.0.0.0 + port_value: 8001 +layered_runtime: + layers: + - name: static_layer_0 + static_layer: + envoy: + resource_limits: + listener: + example_listener_name: + connection_limit: 10000 + overload: + global_downstream_max_connections: 50000 +``` + +### Kyverno-envoy-plugin + +The third component is the `kyverno-envoy-plugin` itself, which is configured to load and enforce Kyverno policies on incoming requests. checkout for [kyverno-envoy-plugin](././manifest/app-envoy-plugin.yaml) + +## Benchmark Scenarios + +The following scenarios should be tested to compare the performance of the `kyverno-envoy-plugin` under different conditions: + +1. **App Only**: Requests are sent directly to the application, without Envoy or the `kyverno-envoy-plugin`. +2. **App and Envoy**: Envoy is included in the request path, but the `kyverno-envoy-plugin` is not (i.e., Envoy External Authorization API is disabled). +3. **App, Envoy, and Kyverno (RBAC policy)**: Envoy External Authorization API is enabled, and a sample real-world RBAC policy is loaded into the `kyverno-envoy-plugin`. + +## Load Testing with k6 + +To perform load testing, we'll use the k6 tool. Follow these steps: + +1. **Install k6**: Install k6 on your machine by following the instructions on the official website: https://k6.io/docs/getting-started/installation/ + +2. **Write the k6 script**: An example script is provided in the repository [k6-script.js](k6-script.js) + +```js +import http from 'k6/http'; +import { check, group, sleep } from 'k6'; + +export const options = { + stages: [ + { duration: '30s', target: 100 }, // Ramp-up to 100 virtual users over 30 seconds + { duration: '1m', target: 100 }, // Stay at 100 virtual users for 1 minute + { duration: '30s', target: 0 }, // Ramp-down to 0 virtual users over 30 seconds + ], +}; + +/* +Replace ip for every scenerio +export SERVICE_PORT=$(kubectl -n demo get service testapp -o jsonpath='{.spec.ports[?(@.port==8080)].nodePort}') +export SERVICE_HOST=$(minikube ip) +export SERVICE_URL=$SERVICE_HOST:$SERVICE_PORT +echo $SERVICE_URL + +http://192.168.49.2:31541 + +*/ +const BASE_URL = 'http://192.168.49.2:31541'; + +export default function () { + group('GET /book with guest token', () => { + const res = http.get(`${BASE_URL}/book`, { + headers: { 'Authorization': 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjIyNDEwODE1MzksIm5iZiI6MTUxNDg1MTEzOSwicm9sZSI6Imd1ZXN0Iiwic3ViIjoiWVd4cFkyVT0ifQ.ja1bgvIt47393ba_WbSBm35NrUhdxM4mOVQN8iXz8lk' }, + }); + check(res, { + 'is status 200': (r) => r.status === 200, + }); + }); + + sleep(1); // Sleep for 1 second between iterations +} +``` + +3. **Run the k6 test**: Run the load test with the following command: + +```shell +$ k6 run -f - < { + const res = http.get(`${BASE_URL}/book`, { + headers: { 'Authorization': 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjIyNDEwODE1MzksIm5iZiI6MTUxNDg1MTEzOSwicm9sZSI6Imd1ZXN0Iiwic3ViIjoiWVd4cFkyVT0ifQ.ja1bgvIt47393ba_WbSBm35NrUhdxM4mOVQN8iXz8lk' }, + }); + check(res, { + 'is status 200': (r) => r.status === 200, + }); + }); + + sleep(1); // Sleep for 1 second between iterations +} +EOF +``` +4. **Analyze the results**: Generate an json report with detailed insight by running: + +```console +k6 run --out json=report.json k6-script.js +``` +5. ***Repeat for different scenarios**: + +- # App only + In this case , request are sent directly to the sample application ie no Envoy and Kyverno-plugin in the request path . + For this run this command to apply the sample applicaition and then test with k6 + + ```shell + $ kubectl apply -f https://raw.githubusercontent.com/kyverno/kyverno-envoy-plugin/main/tests/performance-test/manifest/app.yaml + ``` + Results of the k6 when only application is applied + ```bash + + /\ |‾‾| /‾‾/ /‾‾/ + /\ / \ | |/ / / / + / \/ \ | ( / ‾‾\ + / \ | |\ \ | (‾) | + / __________ \ |__| \__\ \_____/ .io + + execution: local + script: k6-script.js + output: - + + scenarios: (100.00%) 1 scenario, 100 max VUs, 2m30s max duration (incl. graceful stop): + * default: Up to 100 looping VUs for 2m0s over 3 stages (gracefulRampDown: 30s, gracefulStop: 30s) + + + █ GET /book with guest token + + ✓ is status 200 + + checks.........................: 100.00% ✓ 9048 ✗ 0 + data_received..................: 2.1 MB 18 kB/s + data_sent......................: 2.6 MB 21 kB/s + group_duration.................: avg=1.01ms min=166.46µs med=775.01µs max=36ms p(90)=1.72ms p(95)=2.31ms + http_req_blocked...............: avg=15.08µs min=1.55µs med=6.54µs max=4.09ms p(90)=12.07µs p(95)=15.25µs + http_req_connecting............: avg=4.58µs min=0s med=0s max=1.57ms p(90)=0s p(95)=0s + http_req_duration..............: avg=745.73µs min=103.06µs med=549.17µs max=35.88ms p(90)=1.26ms p(95)=1.75ms + { expected_response:true }...: avg=745.73µs min=103.06µs med=549.17µs max=35.88ms p(90)=1.26ms p(95)=1.75ms + http_req_failed................: 0.00% ✓ 0 ✗ 9048 + http_req_receiving.............: avg=119.69µs min=11.33µs med=77.78µs max=10.97ms p(90)=193.73µs p(95)=285.58µs + http_req_sending...............: avg=41µs min=6.96µs med=31.12µs max=2.39ms p(90)=61.88µs p(95)=78.15µs + http_req_tls_handshaking.......: avg=0s min=0s med=0s max=0s p(90)=0s p(95)=0s + http_req_waiting...............: avg=585.04µs min=75.52µs med=407.87µs max=35.84ms p(90)=965.49µs p(95)=1.33ms + http_reqs......................: 9048 75.050438/s + iteration_duration.............: avg=1s min=1s med=1s max=1.06s p(90)=1s p(95)=1s + iterations.....................: 9048 75.050438/s + vus............................: 2 min=2 max=100 + vus_max........................: 100 min=100 max=100 + + +running (2m00.6s), 000/100 VUs, 9048 complete and 0 interrupted iterations +default ✓ [======================================] 000/100 VUs 2m0s + ``` + +- # App and Envoy + In this case, Kyverno-envoy-plugin is not included in the path but Envoy is but Envoy External Authorization API disabled + For this run this command to apply the sample application with envoy. + + ```shell + $ kubectl apply -f https://raw.githubusercontent.com/kyverno/kyverno-envoy-plugin/main/tests/performance-test/manifest/app-envoy.yaml + ``` + + Results of k6 after applying sample-application with envoy. + ```bash + + /\ |‾‾| /‾‾/ /‾‾/ + /\ / \ | |/ / / / + / \/ \ | ( / ‾‾\ + / \ | |\ \ | (‾) | + / __________ \ |__| \__\ \_____/ .io + + execution: local + script: k6-script.js + output: - + + scenarios: (100.00%) 1 scenario, 100 max VUs, 2m30s max duration (incl. graceful stop): + * default: Up to 100 looping VUs for 2m0s over 3 stages (gracefulRampDown: 30s, gracefulStop: 30s) + + + █ GET /book with guest token + + ✓ is status 200 + + checks.........................: 100.00% ✓ 9031 ✗ 0 + data_received..................: 2.5 MB 21 kB/s + data_sent......................: 2.6 MB 21 kB/s + group_duration.................: avg=2.66ms min=457.22µs med=1.8ms max=65.53ms p(90)=4.85ms p(95)=6.58ms + http_req_blocked...............: avg=12.81µs min=1.52µs med=5.98µs max=2.41ms p(90)=11.84µs p(95)=13.9µs + http_req_connecting............: avg=3.82µs min=0s med=0s max=2.34ms p(90)=0s p(95)=0s + http_req_duration..............: avg=2.38ms min=383.7µs med=1.58ms max=65.22ms p(90)=4.36ms p(95)=5.92ms + { expected_response:true }...: avg=2.38ms min=383.7µs med=1.58ms max=65.22ms p(90)=4.36ms p(95)=5.92ms + http_req_failed................: 0.00% ✓ 0 ✗ 9031 + http_req_receiving.............: avg=136.3µs min=12.53µs med=76.74µs max=12.75ms p(90)=183.23µs p(95)=272.91µs + http_req_sending...............: avg=41.54µs min=6.58µs med=28.1µs max=4.15ms p(90)=59.62µs p(95)=74.85µs + http_req_tls_handshaking.......: avg=0s min=0s med=0s max=0s p(90)=0s p(95)=0s + http_req_waiting...............: avg=2.2ms min=349.23µs med=1.43ms max=65.08ms p(90)=4.05ms p(95)=5.52ms + http_reqs......................: 9031 74.825497/s + iteration_duration.............: avg=1s min=1s med=1s max=1.06s p(90)=1s p(95)=1s + iterations.....................: 9031 74.825497/s + vus............................: 3 min=3 max=100 + vus_max........................: 100 min=100 max=100 + + +running (2m00.7s), 000/100 VUs, 9031 complete and 0 interrupted iterations +default ✓ [======================================] 000/100 VUs 2m0s + ``` + +- # App, Envoy and Kyverno-envoy-plugin + In this case, performance measurements are observed with Envoy External Authorization API enabled and a sample real-world RBAC policy loaded into kyverno-envoy-plugin . + For this apply this command to apply sample-application, envoy and kyverno-envoy-plugin + + ```shell + $ kubectl apply -f https://raw.githubusercontent.com/kyverno/kyverno-envoy-plugin/main/tests/performance-test/manifest/app-envoy-plugin.yaml + ``` + + Results of k6 after applying sample-application, Envoy and kyverno-envoy-plugin . + ```console + + /\ |‾‾| /‾‾/ /‾‾/ + /\ / \ | |/ / / / + / \/ \ | ( / ‾‾\ + / \ | |\ \ | (‾) | + / __________ \ |__| \__\ \_____/ .io + + execution: local + script: k6-script.js + output: - + + scenarios: (100.00%) 1 scenario, 100 max VUs, 2m30s max duration (incl. graceful stop): + * default: Up to 100 looping VUs for 2m0s over 3 stages (gracefulRampDown: 30s, gracefulStop: 30s) + + + █ GET /book with guest token + + ✓ is status 200 + + checks.........................: 100.00% ✓ 8655 ✗ 0 + data_received..................: 2.4 MB 20 kB/s + data_sent......................: 2.4 MB 20 kB/s + group_duration.................: avg=46.54ms min=4.59ms med=29.69ms max=337.79ms p(90)=109.35ms p(95)=140.51ms + http_req_blocked...............: avg=11.88µs min=1.21µs med=4.15µs max=2.83ms p(90)=9.87µs p(95)=11.4µs + http_req_connecting............: avg=4.98µs min=0s med=0s max=2.18ms p(90)=0s p(95)=0s + http_req_duration..............: avg=46.37ms min=4.49ms med=29.49ms max=337.69ms p(90)=109.26ms p(95)=140.28ms + { expected_response:true }...: avg=46.37ms min=4.49ms med=29.49ms max=337.69ms p(90)=109.26ms p(95)=140.28ms + http_req_failed................: 0.00% ✓ 0 ✗ 8655 + http_req_receiving.............: avg=65.19µs min=11.14µs med=56.47µs max=5.58ms p(90)=102.86µs p(95)=145.19µs + http_req_sending...............: avg=30.35µs min=5.43µs med=18.48µs max=5.29ms p(90)=46.63µs p(95)=58µs + http_req_tls_handshaking.......: avg=0s min=0s med=0s max=0s p(90)=0s p(95)=0s + http_req_waiting...............: avg=46.27ms min=4.43ms med=29.42ms max=337.65ms p(90)=109.22ms p(95)=140.24ms + http_reqs......................: 8655 71.999297/s + iteration_duration.............: avg=1.04s min=1s med=1.03s max=1.33s p(90)=1.11s p(95)=1.14s + iterations.....................: 8655 71.999297/s + vus............................: 2 min=2 max=100 + vus_max........................: 100 min=100 max=100 + + +running (2m00.2s), 000/100 VUs, 8655 complete and 0 interrupted iterations +default ✓ [======================================] 000/100 VUs 2m0s + ``` +## Measuring Performance + +The following metrics should be measured to evaluate the performance impact of the `kyverno-envoy-plugin`: + +- **End-to-end latency** + The end-to-end latency represents the time taken for a request to complete, from the client sending the request to receiving the response. Based on the k6 results, the average end-to-end latency for the different scenarios is as follows: + + - App Only: `avg=1.01ms` (from `group_duration` or `http_req_duration`) + - App and Envoy: `avg=2.38ms` (from `http_req_duration`) + - App, Envoy, and Kyverno-envoy-plugin: `avg=46.37ms` (from `http_req_duration`) + +- **Kyverno evaluation latency** + The Kyverno evaluation latency represents the time taken by the kyverno-envoy-plugin to evaluate the request against the configured policies. While the k6 results do not directly provide this metric, an estimate can be inferred by analyzing the differences in latency between the "App and Envoy" scenario and the "App, Envoy, and Kyverno-envoy-plugin" scenario. + + The difference in average latency between these two scenarios is: + `46.37ms` - `2.38ms` = `43.99ms` + + This difference can be attributed to the Kyverno evaluation latency and the gRPC server handler latency combined. Assuming the gRPC server handler latency is relatively small compared to the Kyverno evaluation latency, the estimated range for the Kyverno evaluation latency is around 40ms to 45ms. + +- **Resource utilization** + Refers to CPU and memory usage of the Kyverno-Envoy-Plugin container , `kubectl top` utility can be laveraged to measure the resource utilization. + + Get the resource utilization of the kyverno-envoy-plugin container using the following command: + + ```shell + $ kubectl top pod -n demo --containers + ``` + + To monitor resource utilization overtime use the following command: + + ```shell + $ watch -n 1 "kubectl top pod -n demo --containers" + ``` + + Now run the k6 script in different terminal window and observe the resource utilization of the kyverno-envoy-plugin container. + + Initial resource utilization of the kyverno-envoy-plugin container: + + ```console + POD NAME CPU(cores) MEMORY(bytes) + testapp-5955cd6f8b-dbvgd envoy 4m 70Mi + testapp-5955cd6f8b-dbvgd kyverno-envoy-plugin 1m 51Mi + testapp-5955cd6f8b-dbvgd test-application 1m 11Mi + ``` + + Resource utilization of the kyverno-envoy-plugin container after 100 requests: + + ```console + POD NAME CPU(cores) MEMORY(bytes) + testapp-5955cd6f8b-dbvgd envoy 110m 70Mi + testapp-5955cd6f8b-dbvgd kyverno-envoy-plugin 895m 60Mi + testapp-5955cd6f8b-dbvgd test-application 17m 15Mi + + ``` + + Observations: + + - The CPU utilization of the kyverno-envoy-plugin container increased significantly from 1m to 895m after receiving 100 requests during the load test. + - The memory utilization also increased, but to a lesser extent, from 51Mi to 60Mi. + + Resource utilization of the kyverno-envoy-plugin container after load completion: + + ```console + POD NAME CPU(cores) MEMORY(bytes) + testapp-5955cd6f8b-dbvgd envoy 4m 70Mi + testapp-5955cd6f8b-dbvgd kyverno-envoy-plugin 1m 51Mi + testapp-5955cd6f8b-dbvgd test-application 1m 11Mi + ``` + + Observations: + - After the load test completed and the request volume returned to normal levels, the CPU and memory utilization of the kyverno-envoy-plugin container returned to their initial values. This indicates that the kyverno-envoy-plugin can efficiently handle the increased load during the test and release the additional resources when the load subsides. + + Correlation with k6 results: + - The k6 script simulated a load test scenario with 100 virtual users, ramping up over 30 seconds, staying at 100 users for 1 minute, and then ramping down over 30 seconds. + - During the load test, when the request volume was at its peak (100 virtual users), the kyverno-envoy-plugin container experienced a significant increase in CPU utilization, reaching 895m. + - This CPU utilization spike aligns with the increased processing demand on the kyverno-envoy-plugin to evaluate the incoming requests against the configured Kyverno policies. + - The memory utilization increase during the load test was relatively modest, suggesting that the policy evaluation did not significantly impact the memory requirements of the kyverno-envoy-plugin. + + + + + From 823b5b2e0b262d8dd2b3ca0d0822b79d61882785 Mon Sep 17 00:00:00 2001 From: Sanskarzz Date: Sun, 9 Jun 2024 18:20:03 +0530 Subject: [PATCH 3/4] added istio mtls tutorial documentation Signed-off-by: Sanskarzz --- website/docs/tutorials/arch-istio-mtls.png | Bin 0 -> 103868 bytes website/docs/tutorials/mtls-istio.md | 587 +++++++++++++++++++++ 2 files changed, 587 insertions(+) create mode 100644 website/docs/tutorials/arch-istio-mtls.png create mode 100644 website/docs/tutorials/mtls-istio.md diff --git a/website/docs/tutorials/arch-istio-mtls.png b/website/docs/tutorials/arch-istio-mtls.png new file mode 100644 index 0000000000000000000000000000000000000000..1aaa55b6ca6a4d689dc3969ae1e405c4f8c68868 GIT binary patch literal 103868 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

a}Isf+a=UISDSDao*SfRp|*X%m; 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 literal 0 HcmV?d00001 diff --git a/website/docs/tutorials/mtls-istio.md b/website/docs/tutorials/mtls-istio.md new file mode 100644 index 0000000..dcc89b8 --- /dev/null +++ b/website/docs/tutorials/mtls-istio.md @@ -0,0 +1,587 @@ +# Istio mTLS + +Istio is an open source service mesh for managing the different microservices that make up a cloud-native application. Istio provides a mechanism to use a service as an external authorizer with the [AuthorizationPolicy API](https://istio.io/latest/docs/tasks/security/authorization/authz-custom/). + +The kyverno-envoy-plugin is a custom Envoy filter that is used to intercept the incoming request to the service and validate the request using the kyverno engine. + +In this tutorial we will create a two simple microservices which are going to make external authorization to a single kyverno-envoy-plugin service as a separate pod in the mesh. With this tutorial we are going to understand how to use multiple microservices to make authorization decisions to a single ext-authz server. + +![arch-istio-mtls](./arch-istio-mtls.png) + +To handle multiple different requests effectively, we leverage the `match/exclude` declarations to route the specific authz-request to the appropriate validating policy within the Kyverno engine. This approach allows us to execute the right validating policy for each request, enabling efficient and targeted request processing. + +### Example Policy + +The following policies will be executed by the kyverno-envoy-plugin to validate incoming requests made specifically to the `testapp-1` service. By leveraging the match declarations, we ensure that these policies are executed only when the incoming request is destined for the `testapp-1` service. This targeted approach allows us to apply the appropriate validation rules and policies based on the specific service being accessed. + +```yaml +apiVersion: json.kyverno.io/v1alpha1 +kind: ValidatingPolicy +metadata: + name: test-policy +spec: + rules: + - name: deny-external-calls-testapp-1 + match: + any: + - request: + http: + host: 'testapp-1.demo.svc.cluster.local:8080' + assert: + all: + - message: "The GET method is restricted to the /book path." + check: + request: + http: + method: 'GET' + path: '/book' +``` +To execute the policy when the incoming request is made to `testapp-2` service we need to use the `match` declarations. + +```yaml +apiVersion: json.kyverno.io/v1alpha1 +kind: ValidatingPolicy +metadata: + name: test-policy +spec: + rules: + - name: deny-external-calls-testapp-2 + match: + any: + - request: + http: + host: 'testapp-2.demo.svc.cluster.local:8080' + assert: + all: + - message: "The GET method is restricted to the /movies path." + check: + request: + http: + method: 'GET' + path: '/movie' +``` +The example json request for above payload will be like below. + +```json +{ + "source": { + "address": { + "socketAddress": { + "address": "10.244.0.71", + "portValue": 33880 + } + } + }, + "destination": { + "address": { + "socketAddress": { + "address": "10.244.0.65", + "portValue": 8080 + } + } + }, + "request": { + "time": "2024-05-20T07:52:01.566887Z", + "http": { + "id": "5415544797791892902", + "method": "GET", + "headers": { + ":authority": "testapp-2.demo.svc.cluster.local:8080", + ":method": "GET", + ":path": "/movie", + ":scheme": "http", + "user-agent": "Wget", + "x-forwarded-proto": "http", + "x-request-id": "a3ad9f03-c9cd-4eab-97d1-83e90e0cee1b" + }, + "path": "/movie", + "host": "testapp-2.demo.svc.cluster.local:8080", + "scheme": "http", + "protocol": "HTTP/1.1" + } + }, + "metadataContext": {}, + "routeMetadataContext": {} +} +``` + +To enhance security, we can implement Mutual TLS (mTLS) for peer authentication between test services and kyverno-envoy-plugin. Since we are currently using JSON request data to validate incoming requests, there is a potential risk of this data being tampered with during transit. Implementing mTLS would ensure that communication between services is encrypted and authenticated, mitigating the risk of unauthorized data modification. + +```yaml +apiVersion: security.istio.io/v1beta1 +kind: PeerAuthentication +metadata: + name: mtls-demo + namespace: demo +spec: + mtls: + mode: STRICT +--- +apiVersion: security.istio.io/v1beta1 +kind: PeerAuthentication +metadata: + name: mtls-testapp-1 + namespace: demo +spec: + selector: + matchLabels: + app: testapp-1 + mtls: + mode: STRICT + portLevelMtls: + 8080: + mode: PERMISSIVE +--- +apiVersion: security.istio.io/v1beta1 +kind: PeerAuthentication +metadata: + name: mtls-testapp-2 + namespace: demo +spec: + selector: + matchLabels: + app: testapp-2 + mtls: + mode: STRICT + portLevelMtls: + 8080: + mode: PERMISSIVE +``` +## Demo instructions + +### Required tools + +1. [`kind`](https://kind.sigs.k8s.io/) +1. [`kubectl`](https://kubernetes.io/docs/tasks/tools/install-kubectl/) +1. [`helm`](https://helm.sh/docs/intro/install/) + +### Create a local cluster and install Istio + +The tutorial also requries istio v1.19.0 or later. To install istio, follow the instructions [here](https://istio.io/latest/docs/setup/getting-started/) or run the below script it will create a kind cluster and install istio + +```sh +#!/bin/bash + +KIND_IMAGE=kindest/node:v1.29.2 +ISTIO_REPO=https://istio-release.storage.googleapis.com/charts +ISTIO_NS=istio-system + +# Create Kind cluster +kind create cluster --image $KIND_IMAGE --wait 1m --config - < Date: Tue, 11 Jun 2024 19:04:06 +0530 Subject: [PATCH 4/4] removed namespace from quickstart manifest Signed-off-by: Sanskarzz --- quick_start.yaml | 3 --- 1 file changed, 3 deletions(-) diff --git a/quick_start.yaml b/quick_start.yaml index a826c88..9fe3746 100644 --- a/quick_start.yaml +++ b/quick_start.yaml @@ -3,7 +3,6 @@ apiVersion: apps/v1 kind: Deployment metadata: name: testapp - namespace: demo spec: replicas: 1 selector: @@ -89,7 +88,6 @@ apiVersion: v1 kind: ConfigMap metadata: name: proxy-config - namespace: demo data: envoy.yaml: | static_resources: @@ -170,7 +168,6 @@ apiVersion: v1 kind: ConfigMap metadata: name: policy-files - namespace: demo data: policy.yaml: | apiVersion: json.kyverno.io/v1alpha1