feat: add kyverno authz server blog post (#15867)

* feat: add kyverno authz server blog post

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* add kyverno to spelling

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* fix code blocks

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* fix spelling

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* fix spelling

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* update title and description

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* address review comments

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* fix testing commands

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* fix testing commands

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* fix spelling

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* change post date to today

---------

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>
Co-authored-by: Craig Box <craig.box@gmail.com>
This commit is contained in:
Charles-Edouard Brétéché 2024-11-26 00:18:26 +01:00 committed by GitHub
parent 8a840c8a56
commit a49d375b7e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 463 additions and 0 deletions

View File

@ -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
@ -909,6 +912,7 @@ OpenCensus
OpenID
OpenID_Connect
OpenMetrics
OpenPolicyAgent
OpenShift
OpenSSL
openssl

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 206 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 158 KiB

View File

@ -0,0 +1,456 @@
---
title: Policy based authorization using Kyverno
description: Delegate Layer 7 authorization decision logic using Kyverno's Authz Server, leveraging policies based on CEL.
publishdate: 2024-11-25
attribution: "Charles-Edouard Brétéché (Nirmata)"
keywords: [istio,kyverno,policy,platform,authorization]
---
Istio supports integration with many different projects. The Istio blog recently featured a post on [L7 policy functionality with OpenPolicyAgent](../l7-policy-with-opa). Kyverno is a similar project, and today we will dive how Istio and the Kyverno Authz Server can be used together to enforce Layer 7 policies in your platform.
We will show you how to get started with a simple example.
You will come to see how this combination 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
When integrated with Istio, the Kyverno Authz Server can be used to enforce fine-grained access control policies for microservices.
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 - <<EOF
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
spec:
meshConfig:
accessLogFile: /dev/stdout
accessLogFormat: |
[KYVERNO DEMO] my-new-dynamic-metadata: '%DYNAMIC_METADATA(envoy.filters.http.ext_authz)%'
extensionProviders:
- name: kyverno-authz-server
envoyExtAuthzGrpc:
service: kyverno-authz-server.kyverno.svc.cluster.local
port: '9081'
EOF
{{< /text >}}
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
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
$ kubectl label namespace kyverno istio-injection=enabled
$ 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
$ kubectl label namespace my-app istio-injection=enabled
$ 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 - <<EOF
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: my-kyverno-authz
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
rules: [{}] # Empty rules, it will apply to selectors with ext-authz: enabled label
EOF
{{< /text >}}
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
[...]
{{< /text >}}
#### Label the app to enforce the policy
Lets 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": {
"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 - <<EOF
apiVersion: envoy.kyverno.io/v1alpha1
kind: AuthorizationPolicy
metadata:
name: demo-policy.example.com
spec:
failurePolicy: Fail
variables:
- name: force_authorized
expression: object.attributes.request.http.?headers["x-force-authorized"].orValue("")
- name: allowed
expression: variables.force_authorized in ["enabled", "true"]
authorizations:
- expression: >
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.
We 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 apply -n my-app -f {{< github_file >}}/samples/curl/curl.yaml
{{< /text >}}
Apply the policy:
{{< text bash >}}
$ kubectl apply -f - <<EOF
apiVersion: envoy.kyverno.io/v1alpha1
kind: AuthorizationPolicy
metadata:
name: demo-policy.example.com
spec:
failurePolicy: Fail
variables:
- name: force_authorized
expression: object.attributes.request.http.?headers["x-force-authorized"].orValue("")
- name: allowed
expression: variables.force_authorized in ["enabled", "true"]
authorizations:
- expression: >
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 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 deploy/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 - <<EOF
apiVersion: envoy.kyverno.io/v1alpha1
kind: AuthorizationPolicy
metadata:
name: demo-policy.example.com
spec:
variables:
- name: force_authorized
expression: object.attributes.request.http.headers[?"x-force-authorized"].orValue("") in ["enabled", "true"]
- name: force_unauthenticated
expression: object.attributes.request.http.headers[?"x-force-unauthenticated"].orValue("") in ["enabled", "true"]
- name: metadata
expression: '{"my-new-metadata": "my-new-value"}'
authorizations:
# if force_unauthenticated -> 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 deploy/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 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`:
{{< text plain >}}
Authentication Failed
http_code=401
{{< /text >}}
#### Adding headers to request
Running a valid request:
{{< text bash >}}
$ 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:
{{< 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 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`:
{{< 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 deploy/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 >}}
## Conclusion
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.
This is the foundational example for building a platform-wide policy system that can be used by all application teams.

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 194 KiB