From e6dadd4fd9b0c8f820a16dc91c8bfd88b24e2f44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Charles-Edouard=20Br=C3=A9t=C3=A9ch=C3=A9?= Date: Wed, 20 Nov 2024 14:06:08 +0100 Subject: [PATCH 1/3] feat: add kyverno-authz-server integration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Charles-Edouard Brétéché --- .../kyverno-authz-server/dynamic-metadata.svg | 1 + .../kyverno-authz-server/filters-chain.svg | 1 + .../kyverno-authz-server/index.md | 462 ++++++++++++++++++ .../kyverno-authz-server/overview.svg | 1 + 4 files changed, 465 insertions(+) create mode 100644 content/en/docs/ops/integrations/kyverno-authz-server/dynamic-metadata.svg create mode 100644 content/en/docs/ops/integrations/kyverno-authz-server/filters-chain.svg create mode 100644 content/en/docs/ops/integrations/kyverno-authz-server/index.md create mode 100644 content/en/docs/ops/integrations/kyverno-authz-server/overview.svg diff --git a/content/en/docs/ops/integrations/kyverno-authz-server/dynamic-metadata.svg b/content/en/docs/ops/integrations/kyverno-authz-server/dynamic-metadata.svg new file mode 100644 index 0000000000000..e86d327e6181c --- /dev/null +++ b/content/en/docs/ops/integrations/kyverno-authz-server/dynamic-metadata.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/content/en/docs/ops/integrations/kyverno-authz-server/filters-chain.svg b/content/en/docs/ops/integrations/kyverno-authz-server/filters-chain.svg new file mode 100644 index 0000000000000..b59f7756dc979 --- /dev/null +++ b/content/en/docs/ops/integrations/kyverno-authz-server/filters-chain.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/content/en/docs/ops/integrations/kyverno-authz-server/index.md b/content/en/docs/ops/integrations/kyverno-authz-server/index.md new file mode 100644 index 0000000000000..87be380451e18 --- /dev/null +++ b/content/en/docs/ops/integrations/kyverno-authz-server/index.md @@ -0,0 +1,462 @@ +--- +title: Kyverno Authz Server +description: How to integrate with Kyverno Authz Server. +weight: 33 +keywords: [integration,kyverno,policy,authorization] +owner: istio/wg-environments-maintainers +test: no +--- + +The [Kyverno Authz Server](https://kyverno.github.io/kyverno-envoy-plugin) is a GRPC authorization server implementing the [Envoy's External Authorization protocol](https://www.envoyproxy.io/docs/envoy/latest/api-v3/service/auth/v3/external_auth.proto). + +Istio and the Kyverno Authz Server is a solid option to deliver policy quickly and transparently to application team everywhere in the business, while also providing the data the security teams need for audit and compliance. + +## Try it out + +This guide shows how to enforce access control policies for a simple microservices application. + +### Prerequisites + +- A Kubernetes cluster with Istio installed. +- The `istioctl` command-line tool installed. + +Install Istio and configure your [mesh options](/docs/reference/config/istio.mesh.v1alpha1/) to enable Kyverno: + +{{< text bash >}} +$ istioctl install -y -f - <}} + +Notice that in the configuration, we define an `extensionProviders` section that points to the Kyverno Authz Server installation: + +{{< text yaml >}} +[...] + extensionProviders: + - name: kyverno-authz-server.local + envoyExtAuthzGrpc: + service: kyverno-authz-server.kyverno.svc.cluster.local + port: '9081' +[...] +{{< /text >}} + +#### Deploy the Kyverno Authz Server + +The Kyverno Authz Server is a GRPC server capable of processing Envoy External Authorization requests. + +It is configurable using Kyverno `AuthorizationPolicy` resources, either stored in-cluster or provided externally. + +{{< text bash >}} +$ kubectl create ns kyverno +{{< /text >}} + +{{< text bash >}} +$ kubectl label namespace kyverno istio-injection=enabled +{{< /text >}} + +{{< text bash >}} +$ helm install kyverno-authz-server --namespace kyverno --wait --repo https://kyverno.github.io/kyverno-envoy-plugin kyverno-authz-server +{{< /text >}} + +#### Deploy the sample application + +Httpbin is a well-known application that can be used to test HTTP requests and helps to show quickly how we can play with the request and response attributes. + +{{< text bash >}} +$ kubectl create ns my-app +{{< /text >}} + +{{< text bash >}} +$ kubectl label namespace my-app istio-injection=enabled +{{< /text >}} + +{{< text bash >}} +$ kubectl apply -f {{< github_file >}}/samples/httpbin/httpbin.yaml -n my-app +{{< /text >}} + +#### Deploy an Istio AuthorizationPolicy + +An `AuthorizationPolicy` defines the services that will be protected by the Kyverno Authz Server. + +{{< text bash >}} +$ kubectl apply -f - <}} + +Notice that in this resource, we define the Kyverno Authz Server `extensionProvider` you set in the Istio configuration: + +{{< text yaml >}} +[...] + provider: + name: kyverno-authz-server.local +[...] +{{< /text >}} + +#### Label the app to enforce the policy + +{{< text bash >}} +$ kubectl patch deploy httpbin -n my-app --type=merge -p='{ + "spec": { + "template": { + "metadata": { + "labels": { + "ext-authz": "enabled" + } + } + } + } +}' +{{< /text >}} + +#### Deploy a Kyverno AuthorizationPolicy + +A Kyverno `AuthorizationPolicy` defines the rules used by the Kyverno Authz Server to make a decision based on a given Envoy [CheckRequest](https://www.envoyproxy.io/docs/envoy/latest/api-v3/service/auth/v3/external_auth.proto#service-auth-v3-checkrequest). + +It uses the [CEL language](https://github.com/google/cel-spec) to analyze an incoming `CheckRequest` and is expected to produce a [CheckResponse](https://www.envoyproxy.io/docs/envoy/latest/api-v3/service/auth/v3/external_auth.proto#service-auth-v3-checkresponse) in return. + +The incoming request is available under the `object` field, and the policy can define `variables` that will be made available to all `authorizations`. + +{{< text bash >}} +$ kubectl apply -f - < + variables.allowed + ? envoy.Allowed().Response() + : envoy.Denied(403).Response() +EOF +{{< /text >}} + +Notice that you can build the `CheckResponse` by hand or use [CEL helper functions](https://kyverno.github.io/kyverno-envoy-plugin/latest/cel-extensions/) like `envoy.Allowed()` and `envoy.Denied(403)` to simplify creating the response message: + +{{< text yaml >}} +[...] + - expression: > + variables.allowed + ? envoy.Allowed().Response() + : envoy.Denied(403).Response() +[...] +{{< /text >}} + +## How it works + +When applying the `AuthorizationPolicy`, the Istio control plane (istiod) sends the required configurations to the sidecar proxy (Envoy) of the selected services in the policy. +Envoy will then send the request to the Kyverno Authz Server to check if the request is allowed or not. + +{{< image width="75%" link="./overview.svg" alt="Istio and Kyverno Authz Server" >}} + +The Envoy proxy works by configuring filters in a chain. One of those filters is `ext_authz`, which implements an external authorization service with a specific message. Any server implementing the correct protobuf can connect to the Envoy proxy and provide the authorization decision; The Kyverno Authz Server is one of those servers. + +{{< image link="./filters-chain.svg" alt="Filters" >}} + +Reviewing [Envoy's Authorization service documentation](https://www.envoyproxy.io/docs/envoy/latest/api-v3/service/auth/v3/external_auth.proto), you can see that the message has these attributes: + +- Ok response + + {{< text json >}} + { + "status": {...}, + "ok_response": { + "headers": [], + "headers_to_remove": [], + "response_headers_to_add": [], + "query_parameters_to_set": [], + "query_parameters_to_remove": [] + }, + "dynamic_metadata": {...} + } + {{< /text >}} + +- Denied response + + {{< text json >}} + { + "status": {...}, + "denied_response": { + "status": {...}, + "headers": [], + "body": "..." + }, + "dynamic_metadata": {...} + } + {{< /text >}} + +This means that based on the response from the authz server, Envoy can add or remove headers, query parameters, and even change the response body. + +The Kyverno Authz Server can do this as well, as documented in the [Kyverno Authz Server documentation](https://kyverno.github.io/kyverno-envoy-plugin). + +## Testing + +Let's test the simple usage (authorization) and then let's create a more advanced policy to show how we can use the Kyverno Authz Server to modify the request and response. + +Deploy an app to run curl commands to the httpbin sample application: + +{{< text bash >}} +$ kubectl -n my-app run --image=curlimages/curl curl -- /bin/sleep 100d +{{< /text >}} + +Apply the policy: + +{{< text bash >}} +$ kubectl apply -f - < + variables.allowed + ? envoy.Allowed().Response() + : envoy.Denied(403).Response() +EOF +{{< /text >}} + +The simple scenario is to allow requests if they contain the header `x-force-authorized` with the value `enabled` or `true`. +If the header is not present or has a different value, the request will be denied. + +In this case, we combined allow and denied response handling in a single expression. However it is possible to use multiple expressions, the first one returning a non null response will be used by the Kyverno Authz Server, this is useful when a rule doesn't want to make a decision and delegate to the next rule: + +{{< text yaml >}} +[...] + authorizations: + # allow the request when the header value matches + - expression: > + variables.allowed + ? envoy.Allowed().Response() + : null + # else deny the request + - expression: > + envoy.Denied(403).Response() +[...] +{{< /text >}} + +### Simple rule + +The following request will return `403`: + +{{< text bash >}} +$ kubectl exec -n my-app curl -c curl -- curl -s -w "\nhttp_code=%{http_code}" httpbin:8000/get +{{< /text >}} + +The following request will return `200`: + +{{< text bash >}} +$ kubectl exec -n my-app curl -c curl -- curl -s -w "\nhttp_code=%{http_code}" httpbin:8000/get -H "x-force-authorized: true" +{{< /text >}} + +### Advanced manipulations + +Now the more advanced use case, apply the second policy: + +{{< text bash >}} +$ kubectl apply -f - < 401 + - expression: > + variables.force_unauthenticated + ? envoy + .Denied(401) + .WithBody("Authentication Failed") + .Response() + : null + # if force_authorized -> 200 + - expression: > + variables.force_authorized + ? envoy + .Allowed() + .WithHeader("x-validated-by", "my-security-checkpoint") + .WithoutHeader("x-force-authorized") + .WithResponseHeader("x-add-custom-response-header", "added") + .Response() + .WithMetadata(variables.metadata) + : null + # else -> 403 + - expression: > + envoy + .Denied(403) + .WithBody("Unauthorized Request") + .Response() +EOF +{{< /text >}} + +In that policy, you can see: + +- If the request has the `x-force-unauthenticated: true` header (or `x-force-unauthenticated: enabled`), we will return `401` with the "Authentication Failed" body +- Else, if the request has the `x-force-authorized: true` header (or `x-force-authorized: enabled`), we will return `200` and manipulate request headers, response headers and inject dynamic metadata +- In all other cases, we will return `403` with the "Unauthorized Request" body + +The corresponding CheckResponse will be returned to the Envoy proxy from the Kyverno Authz Server. Envoy will use those values to modify the request and response accordingly. + +#### Change returned body + +Let's test the new capabilities: + +{{< text bash >}} +$ kubectl exec -n my-app curl -c curl -- curl -s -w "\nhttp_code=%{http_code}" httpbin:8000/get +{{< /text >}} + +Now we can change the response body. + +With `403` the body will be changed to "Unauthorized Request", running the previous command, you should receive: + +{{< text plain >}} +Unauthorized Request +http_code=403 +{{< /text >}} + +#### Change returned body and status code + +Running the request with the header `x-force-unauthenticated: true`: + +{{< text bash >}} +$ kubectl exec -n my-app curl -c curl -- curl -s -w "\nhttp_code=%{http_code}" httpbin:8000/get -H "x-force-unauthenticated: true" +{{< /text >}} + +This time you should receive the body "Authentication Failed" and error `401`: + +{{< text plain >}} +Authentication Failed +http_code=401 +{{< /text >}} + +#### Adding headers to request + +Running a valid request: + +{{< text bash >}} +$ kubectl exec -n my-app curl -c curl -- curl -s -w "\nhttp_code=%{http_code}" httpbin:8000/get -H "x-force-authorized: true" +{{< /text >}} + +You should receive the echo body with the new header `x-validated-by: my-security-checkpoint` and the header `x-force-authorized` removed: + +{{< text plain >}} +[...] + "X-Validated-By": [ + "my-security-checkpoint" + ] +[...] +http_code=200 +{{< /text >}} + +#### Adding headers to response + +Running the same request but showing only the header: + +{{< text bash >}} +$ kubectl exec -n my-app curl -c curl -- curl -s -I -w "\nhttp_code=%{http_code}" httpbin:8000/get -H "x-force-authorized: true" +{{< /text >}} + +You will find the response header added during the Authz check `x-add-custom-response-header: added`: + +{{< text plain >}} +HTTP/1.1 200 OK +[...] +x-add-custom-response-header: added +[...] +http_code=200 +{{< /text >}} + +### Sharing data between filters + +Finally, you can pass data to the following Envoy filters using `dynamic_metadata`. + +This is useful when you want to pass data to another `ext_authz` filter in the chain or you want to print it in the application logs. + +{{< image link="./dynamic-metadata.svg" alt="Metadata" >}} + +To do so, review the access log format you set earlier: + +{{< text plain >}} +[...] + accessLogFormat: | + [KYVERNO DEMO] my-new-dynamic-metadata: "%DYNAMIC_METADATA(envoy.filters.http.ext_authz)%" +[...] +{{< /text >}} + +`DYNAMIC_METADATA` is a reserved keyword to access the metadata object. The rest is the name of the filter that you want to access. + +In our case, the name `envoy.filters.http.ext_authz` is created automatically by Istio. You can verify this by dumping the Envoy configuration: + +{{< text bash >}} +$ istioctl pc all deploy/httpbin -n my-app -oyaml | grep envoy.filters.http.ext_authz +{{< /text >}} + +You will see the configurations for the filter. + +Let's test the dynamic metadata. In the advance rule, we are creating a new metadata entry: `{"my-new-metadata": "my-new-value"}`. + +Run the request and check the logs of the application: + +{{< text bash >}} +$ kubectl exec -n my-app curl -c curl -- curl -s -I httpbin:8000/get -H "x-force-authorized: true" +{{< /text >}} + +{{< text bash >}} +$ kubectl logs -n my-app deploy/httpbin -c istio-proxy --tail 1 +{{< /text >}} + +You will see in the output the new attributes configured by the Kyverno policy: + +{{< text plain >}} +[...] +[KYVERNO DEMO] my-new-dynamic-metadata: '{"my-new-metadata":"my-new-value","ext_authz_duration":5}' +[...] +{{< /text >}} + +## Wrap Up + +In this guide, we have shown how to integrate Istio and the Kyverno Authz Server to enforce policies for a simple microservices application. +We also showed how to use policies to modify the request and response attributes. diff --git a/content/en/docs/ops/integrations/kyverno-authz-server/overview.svg b/content/en/docs/ops/integrations/kyverno-authz-server/overview.svg new file mode 100644 index 0000000000000..dfd06e4a01a5d --- /dev/null +++ b/content/en/docs/ops/integrations/kyverno-authz-server/overview.svg @@ -0,0 +1 @@ + \ No newline at end of file From aa7a09e2b73bfc68676f08ec6092e768a0bc69f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Charles-Edouard=20Br=C3=A9t=C3=A9ch=C3=A9?= Date: Fri, 22 Nov 2024 10:14:56 +0100 Subject: [PATCH 2/3] fix spelling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Charles-Edouard Brétéché --- .spelling | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.spelling b/.spelling index b10f7a0525ef5..37e496612486c 100644 --- a/.spelling +++ b/.spelling @@ -228,6 +228,8 @@ Cernich CFP Chaomeng Chavali +CheckRequest +CheckResponse checksum Chircop Chrony @@ -750,6 +752,7 @@ Kumar Kustomization Kustomize kustomize +Kyverno kyzy L2-L4 L3-4 From f67e22e2b45854c22d11223fef47233f1e82889d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Charles-Edouard=20Br=C3=A9t=C3=A9ch=C3=A9?= Date: Fri, 22 Nov 2024 10:24:34 +0100 Subject: [PATCH 3/3] review fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Charles-Edouard Brétéché --- .../kyverno-authz-server/index.md | 32 ++++++++++--------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/content/en/docs/ops/integrations/kyverno-authz-server/index.md b/content/en/docs/ops/integrations/kyverno-authz-server/index.md index 87be380451e18..955e44d03a4e8 100644 --- a/content/en/docs/ops/integrations/kyverno-authz-server/index.md +++ b/content/en/docs/ops/integrations/kyverno-authz-server/index.md @@ -32,7 +32,7 @@ spec: accessLogFormat: | [KYVERNO DEMO] my-new-dynamic-metadata: '%DYNAMIC_METADATA(envoy.filters.http.ext_authz)%' extensionProviders: - - name: kyverno-authz-server.local + - name: kyverno-authz-server envoyExtAuthzGrpc: service: kyverno-authz-server.kyverno.svc.cluster.local port: '9081' @@ -44,7 +44,7 @@ Notice that in the configuration, we define an `extensionProviders` section that {{< text yaml >}} [...] extensionProviders: - - name: kyverno-authz-server.local + - name: kyverno-authz-server envoyExtAuthzGrpc: service: kyverno-authz-server.kyverno.svc.cluster.local port: '9081' @@ -71,7 +71,7 @@ $ helm install kyverno-authz-server --namespace kyverno --wait --repo https://ky #### Deploy the sample application -Httpbin is a well-known application that can be used to test HTTP requests and helps to show quickly how we can play with the request and response attributes. +httpbin is a well-known application that can be used to test HTTP requests and helps to show quickly how we can play with the request and response attributes. {{< text bash >}} $ kubectl create ns my-app @@ -95,14 +95,14 @@ apiVersion: security.istio.io/v1 kind: AuthorizationPolicy metadata: name: my-kyverno-authz - namespace: istio-system # This enforce the policy on all the mesh being istio-system the mesh config namespace + namespace: istio-system # This enforce the policy on all the mesh, istio-system being the mesh root namespace spec: selector: matchLabels: ext-authz: enabled action: CUSTOM provider: - name: kyverno-authz-server.local + name: kyverno-authz-server rules: [{}] # Empty rules, it will apply to selectors with ext-authz: enabled label EOF {{< /text >}} @@ -112,12 +112,14 @@ Notice that in this resource, we define the Kyverno Authz Server `extensionProvi {{< text yaml >}} [...] provider: - name: kyverno-authz-server.local + name: kyverno-authz-server [...] {{< /text >}} #### Label the app to enforce the policy +Let’s label the app to enforce the policy. The label is needed for the Istio `AuthorizationPolicy` to apply to the sample application pods. + {{< text bash >}} $ kubectl patch deploy httpbin -n my-app --type=merge -p='{ "spec": { @@ -217,7 +219,7 @@ Reviewing [Envoy's Authorization service documentation](https://www.envoyproxy.i This means that based on the response from the authz server, Envoy can add or remove headers, query parameters, and even change the response body. -The Kyverno Authz Server can do this as well, as documented in the [Kyverno Authz Server documentation](https://kyverno.github.io/kyverno-envoy-plugin). +We can do this as well, as documented in the [Kyverno Authz Server documentation](https://kyverno.github.io/kyverno-envoy-plugin). ## Testing @@ -226,7 +228,7 @@ Let's test the simple usage (authorization) and then let's create a more advance Deploy an app to run curl commands to the httpbin sample application: {{< text bash >}} -$ kubectl -n my-app run --image=curlimages/curl curl -- /bin/sleep 100d +$ kubectl apply -n my-app run -f {{< github_file >}}/samples/curl/curl.yaml {{< /text >}} Apply the policy: @@ -276,13 +278,13 @@ In this case, we combined allow and denied response handling in a single express The following request will return `403`: {{< text bash >}} -$ kubectl exec -n my-app curl -c curl -- curl -s -w "\nhttp_code=%{http_code}" httpbin:8000/get +$ kubectl exec -n my-app deploy/curl -- curl -s -w "\nhttp_code=%{http_code}" httpbin:8000/get {{< /text >}} The following request will return `200`: {{< text bash >}} -$ kubectl exec -n my-app curl -c curl -- curl -s -w "\nhttp_code=%{http_code}" httpbin:8000/get -H "x-force-authorized: true" +$ kubectl exec -n my-app deploy/curl -- curl -s -w "\nhttp_code=%{http_code}" httpbin:8000/get -H "x-force-authorized: true" {{< /text >}} ### Advanced manipulations @@ -345,7 +347,7 @@ The corresponding CheckResponse will be returned to the Envoy proxy from the Kyv Let's test the new capabilities: {{< text bash >}} -$ kubectl exec -n my-app curl -c curl -- curl -s -w "\nhttp_code=%{http_code}" httpbin:8000/get +$ kubectl exec -n my-app deploy/curl -- curl -s -w "\nhttp_code=%{http_code}" httpbin:8000/get {{< /text >}} Now we can change the response body. @@ -362,7 +364,7 @@ http_code=403 Running the request with the header `x-force-unauthenticated: true`: {{< text bash >}} -$ kubectl exec -n my-app curl -c curl -- curl -s -w "\nhttp_code=%{http_code}" httpbin:8000/get -H "x-force-unauthenticated: true" +$ kubectl exec -n my-app deploy/curl -- curl -s -w "\nhttp_code=%{http_code}" httpbin:8000/get -H "x-force-unauthenticated: true" {{< /text >}} This time you should receive the body "Authentication Failed" and error `401`: @@ -377,7 +379,7 @@ http_code=401 Running a valid request: {{< text bash >}} -$ kubectl exec -n my-app curl -c curl -- curl -s -w "\nhttp_code=%{http_code}" httpbin:8000/get -H "x-force-authorized: true" +$ kubectl exec -n my-app deploy/curl -- curl -s -w "\nhttp_code=%{http_code}" httpbin:8000/get -H "x-force-authorized: true" {{< /text >}} You should receive the echo body with the new header `x-validated-by: my-security-checkpoint` and the header `x-force-authorized` removed: @@ -396,7 +398,7 @@ http_code=200 Running the same request but showing only the header: {{< text bash >}} -$ kubectl exec -n my-app curl -c curl -- curl -s -I -w "\nhttp_code=%{http_code}" httpbin:8000/get -H "x-force-authorized: true" +$ kubectl exec -n my-app deploy/curl -- curl -s -I -w "\nhttp_code=%{http_code}" httpbin:8000/get -H "x-force-authorized: true" {{< /text >}} You will find the response header added during the Authz check `x-add-custom-response-header: added`: @@ -441,7 +443,7 @@ Let's test the dynamic metadata. In the advance rule, we are creating a new meta Run the request and check the logs of the application: {{< text bash >}} -$ kubectl exec -n my-app curl -c curl -- curl -s -I httpbin:8000/get -H "x-force-authorized: true" +$ kubectl exec -n my-app deploy/curl -- curl -s -I httpbin:8000/get -H "x-force-authorized: true" {{< /text >}} {{< text bash >}}