Envoy Gateway¶
Envoy Gateway is an open source project for managing Envoy Proxy as a standalone or Kubernetes-based application gateway. Gateway API resources are used to dynamically provision and configure the managed Envoy Proxies.
This tutorial shows how Envoy Gateway can be configured to delegate authorization decisions to the Kyverno Authz Server.
Setup¶
Prerequisites¶
- A Kubernetes cluster
- Helm to install Envoy Gateway the Kyverno Authz Server
- kubectl to interact with the cluster
Setup a cluster (optional)¶
If you don't have a cluster at hand, you can create a local one with kind.
KIND_IMAGE=kindest/node:v1.31.1
# create cluster
kind create cluster --image $KIND_IMAGE --wait 1m
Install Envoy Gateway¶
First we need to install Envoy Gateway in the cluster.
# install envoy gateway
helm install envoy-gateway -n envoy-gateway-system --create-namespace --wait --version v1.2.2 oci://docker.io/envoyproxy/gateway-helm
Deploy a 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.
# create the demo namespace
kubectl create ns demo
# deploy the httpbin application
kubectl apply -n demo -f https://raw.githubusercontent.com/istio/istio/master/samples/httpbin/httpbin.yaml
Create a GatewayClass and a Gateway¶
With Envoy Gateway installed we can now create a Gateway
. To do so we will also create a dedicated GatewayClass
.
Depending on your setup you will potentially need to create an EnvoyProxy
resource to customize the way Envoy Gateway will create the underlying Service
. The script below creates one to set the name and type of the service because the kind cluster created in the first step doesn't come with load balancer support.
# create a gateway
kubectl apply -n demo -f - <<EOF
apiVersion: gateway.envoyproxy.io/v1alpha1
kind: EnvoyProxy
metadata:
name: demo
spec:
provider:
type: Kubernetes
kubernetes:
envoyService:
name: internet # use a known name for the created service
type: ClusterIP # because a kind cluster has no support for LB
---
apiVersion: gateway.networking.k8s.io/v1
kind: GatewayClass
metadata:
name: demo
spec:
controllerName: gateway.envoyproxy.io/gatewayclass-controller
---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: demo
spec:
gatewayClassName: demo
infrastructure:
parametersRef:
group: gateway.envoyproxy.io
kind: EnvoyProxy
name: demo
listeners:
- name: http
protocol: HTTP
port: 80
EOF
Create an HTTPRoute to the sample application¶
Next, we need to link the Gateway to our sample applicate with an HTTPRoute
.
# create an http route to the sample app
kubectl apply -n demo -f - <<EOF
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: demo
spec:
parentRefs:
- name: demo
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- group: ''
kind: Service
name: httpbin
port: 8000
weight: 1
EOF
Deploy the Kyverno Authz Server¶
Now deploy the Kyverno Authz Server.
# deploy the kyverno authz server
helm install kyverno-authz-server --namespace kyverno --create-namespace --wait --repo https://kyverno.github.io/kyverno-envoy-plugin kyverno-authz-server
Create a Kyverno AuthorizationPolicy¶
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
# deploy kyverno authorization policy
kubectl apply -f - <<EOF
apiVersion: envoy.kyverno.io/v1alpha1
kind: AuthorizationPolicy
metadata:
name: demo
spec:
failurePolicy: Fail
variables:
- name: authorization
expression: object.attributes.request.http.headers[?"authorization"].orValue("").split(" ")
- name: token
expression: >
size(variables.authorization) == 2 && variables.authorization[0].lowerAscii() == "bearer"
? jwt.Decode(variables.authorization[1], "secret")
: null
authorizations:
# request not authenticated -> 401
- expression: >
variables.token == null || !variables.token.Valid
? envoy.Denied(401).Response()
: null
# request authenticated but not admin role -> 403
- expression: >
variables.token.Claims.?role.orValue("") != "admin"
? envoy.Denied(403).Response()
: null
# request authenticated and admin role -> 200
- expression: >
envoy
.Allowed()
.WithHeader("x-validated-by", "my-security-checkpoint")
.WithoutHeader("x-force-authorized")
.WithResponseHeader("x-add-custom-response-header", "added")
.Response()
EOF
Deploy an Envoy Gateway SecurityPolicy¶
A SecurityPolicy
is the custom Envoy Gateway resource to configure underlying Envoy Proxy to use an external auth server (the Kyverno Authz Server we installed in a prior step).
# deploy envoy gateway security policy
kubectl apply -n demo -f - <<EOF
apiVersion: gateway.envoyproxy.io/v1alpha1
kind: SecurityPolicy
metadata:
name: demo
spec:
targetRefs:
- group: gateway.networking.k8s.io
kind: HTTPRoute
name: demo
extAuth:
grpc:
backendRef:
group: ''
kind: Service
name: kyverno-authz-server
namespace: kyverno
port: 9081
EOF
Notice that in this resource, we define the Kyverno Authz Server service as the GRPC backend:
[...]
extAuth:
grpc:
backendRef:
group: ''
kind: Service
name: kyverno-authz-server
namespace: kyverno
port: 9081
[...]
Also notice that the security policy applies to the demo
HTTPRoute:
[...]
targetRefs:
- group: gateway.networking.k8s.io
kind: HTTPRoute
name: demo
[...]
Grant access to the Kyverno Authz Server service¶
Last thing we need to configure is to grant access to the Kyverno Authz Server service for our SecurityPolicy to take effect.
# grant access
kubectl apply -n kyverno -f - <<EOF
apiVersion: gateway.networking.k8s.io/v1beta1
kind: ReferenceGrant
metadata:
name: demo
spec:
from:
- group: gateway.envoyproxy.io
kind: SecurityPolicy
namespace: demo
to:
- group: ''
kind: Service
EOF
Testing¶
At this we have deployed and configured Envoy Gateway, the Kyverno Authz Server, a sample application, and the authorization and security policies.
Start an in-cluster shell¶
Let's start a pod in the cluster with a shell to call into the sample application.
# run an in-cluster shell
kubectl run -i -t busybox --image=alpine --restart=Never -n demo
Install curl¶
We will use curl to call into the sample application but it's not installed in our shell, let's install it in the pod.
# install curl
apk add curl
Call into the sample application¶
Now we can send request to the sample application and verify the result.
For convenience, we will store Alice’s and Bob’s tokens in environment variables.
Here Bob is assigned the admin role and Alice is assigned the guest role.
export ALICE_TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjIyNDEwODE1MzksIm5iZiI6MTUxNDg1MTEzOSwicm9sZSI6Imd1ZXN0Iiwic3ViIjoiWVd4cFkyVT0ifQ.ja1bgvIt47393ba_WbSBm35NrUhdxM4mOVQN8iXz8lk"
export BOB_TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjIyNDEwODE1MzksIm5iZiI6MTUxNDg1MTEzOSwicm9sZSI6ImFkbWluIiwic3ViIjoiWVd4cFkyVT0ifQ.veMeVDYlulTdieeX-jxFZ_tCmqQ_K8rwx2OktUHv5Z0"
Calling without a JWT token will return 401
:
curl -s -w "\nhttp_code=%{http_code}" internet.envoy-gateway-system/get
Calling with Alice’s JWT token will return 403
:
curl -s -w "\nhttp_code=%{http_code}" internet.envoy-gateway-system/get -H "authorization: Bearer $ALICE_TOKEN"
Calling with Bob’s JWT token will return 200
:
curl -s -w "\nhttp_code=%{http_code}" internet.envoy-gateway-system/get -H "authorization: Bearer $BOB_TOKEN"
Wrap Up¶
Congratulations on completing the tutorial!
This tutorial demonstrated how to configure Envoy Gateway to utilize the Kyverno Authz Server as an external authorization service.
Additionally, the tutorial provided an example policy to decode a JWT token and make a decision based on it.