diff --git a/gloo-edge/gloo-edge/README.md b/gloo-edge/gloo-edge/README.md index 69f5c093d6..a2b8f667c2 100644 --- a/gloo-edge/gloo-edge/README.md +++ b/gloo-edge/gloo-edge/README.md @@ -9,14 +9,15 @@ The goal of this workshop is to expose some key features of Gloo API Gateway, li The lab environment consists of a Kubernetes environment deployed locally using kind. In this workshop we will: -* Deploy a demo application (Istio's [bookinfo](https://istio.io/latest/docs/examples/bookinfo/) demo app) on a k8s cluster and expose it through Gloo Edge -* Deploy a second version of the demo app and route traffic to both versions -* Secure the demo app using TLS -* Secure the demo app using OIDC -* Rate limit the traffic going to the demo app -* Transform a response using Gloo transformations +* Deploy two applications (Istio's **[bookinfo](https://istio.io/latest/docs/examples/bookinfo/)** and **[httpbin](https://github.com/istio/istio/blob/master/samples/httpbin/httpbin.yaml)**) on a k8s cluster and expose it through Gloo Edge +* Deploy a second version of the **bookinfo** app and route traffic to both versions +* Secure apps using TLS +* Apply Authentication using OIDC +* Rate limit the traffic going to the apps +* Apply rules for the Web Application Firewall (WAF) +* Transform requests, responses and bodies using Gloo transformations +* Apply Authorization OPA (Open Policy Agent) * Configure access logs -* Use several of these features together to configure an advanced OIDC workflow ## Lab 0: Demo Environment Creation @@ -47,8 +48,8 @@ Run the commands below to deploy Gloo Edge Enterprise: ```bash kubectl config use-context gloo-edge -glooctl upgrade --release=v1.7.2 -glooctl install gateway enterprise --version 1.7.2 --license-key $LICENSE_KEY +glooctl upgrade --release=v1.8.0 +glooctl install gateway enterprise --version 1.8.0 --license-key $LICENSE_KEY ``` Gloo Edge can also be deployed using a Helm chart. @@ -69,11 +70,11 @@ done ## Lab 1: Traffic Management -### Routing to a Kubernetes Service +### Deploy Services -In this step we will expose a demo service to the outside world using Gloo Edge. +In this step you will expose two services to the outside world using Gloo Edge. -First let's deploy a demo application called bookinfo: +First let's deploy an application called **bookinfo** in `bookinfo` namespace: ```bash kubectl create ns bookinfo @@ -83,7 +84,7 @@ kubectl delete deployment reviews-v1 reviews-v3 -n bookinfo ![Gloo Edge with Bookinfo](images/bookinfo-v2.png) -The bookinfo app has 3 versions of a microservice called reviews. We will keep only the version 2 of the reviews microservice for this step and will add the other versions later. An easy way to distinguish among the different versions in the web interface is to look at the stars: v1 displays no stars in the reviews, v2 displays black stars, and v3 displays red stars. +The bookinfo app has 3 versions of a microservice called reviews. You will keep only the version 2 of the reviews microservice for this step and will add the other versions later. An easy way to distinguish among the different versions in the web interface is to look at the stars: v1 displays no stars in the reviews, v2 displays black stars, and v3 displays red stars. Gloo Edge uses a discovery mechanism to create Upstreams automatically, but Upstreams can be also created manually using Kubernetes CRDs. @@ -113,7 +114,94 @@ It should return the discovered upstream with an `Accepted` status: +---------------------------+------------+----------+----------------------------+ ``` -Now that the Upstream CRD has been created, we need to create a Gloo Edge Virtual Service that routes traffic to it: +Now, let's deploy the second application, called **httpbin** in the namespace `team1`. + +This application is very useful when you have to debug routing, headers in requests, responses, status codes, etc. The public online version of it can be found [here](http://httpbin.org/). + +```bash +kubectl create ns team1 + + +kubectl apply -f - < /dev/null +do + echo waiting for upstream team1-httpbin-8000 to be discovered + sleep 3 +done +``` + +It should return the discovered upstream with an `Accepted` status: + +``` ++--------------------+------------+----------+------------------------+ +| UPSTREAM | TYPE | STATUS | DETAILS | ++--------------------+------------+----------+------------------------+ +| team1-httpbin-8000 | Kubernetes | Accepted | svc name: httpbin | +| | | | svc namespace: team1 | +| | | | port: 8000 | +| | | | | ++--------------------+------------+----------+------------------------+ +``` + + +### Routing to a Kubernetes Service + +Now that your two Upstream CR have been created, you need to create the resources to route the traffic to them. + +First, you create a Virtual Service. For bookinfo under `/` and for httpbin under `/not-secured`. + +Please, notice that the order matters. ```bash kubectl apply -f - < Since you are using self-signed certificates, you need to allow insecure server connection when using SSL with *-k* -```bash -kubectl create -f https://raw.githubusercontent.com/keycloak/keycloak-quickstarts/12.0.4/kubernetes-examples/keycloak.yaml -kubectl rollout status deploy/keycloak ``` -**Troubleshooting Tip** +curl -k $APP_URL/not-secured/get +``` +### Authentication with OIDC (OpenID Connect) + +In many use cases, you need to restrict the access to your applications to authenticated users. -Make sure that the KeyCloak deployment worked correctly by executing the following. +OIDC (OpenID Connect) is an identity layer on top of the OAuth 2.0 protocol. In OAuth 2.0 flows, authentication is performed by an external Identity Provider (IdP) which, in case of success, returns an Access Token representing the user identity. The protocol does not define the contents and structure of the Access Token, which greatly reduces the portability of OAuth 2.0 implementations. + +The goal of OIDC is to address this ambiguity by additionally requiring Identity Providers to return a well-defined ID Token. OIDC ID tokens follow the JSON Web Token standard and contain specific fields that your applications can expect and handle. This standardization allows you to switch between Identity Providers – or support multiple ones at the same time – with minimal, if any, changes to your downstream services; it also allows you to consistently apply additional security measures like Role-based Access Control (RBAC) based on the identity of your users, i.e. the contents of their ID token. -`kubectl describe pod keycloak` -If you see an issue with the readiness probe failing, then you will want to edit the deployment and add the following values under the readinessProbe. +In this step, you will secure the **bookinfo** application using an OIDC Identity Provider. -`kubectl patch deployment keycloak --type='json' -p='[{"op": "replace", "path": "/spec/template/spec/containers/0/readinessProbe/timeoutSeconds", "value":10}]'` +Let's start by installing Keycloak: -`kubectl patch deployment keycloak --type='json' -p='[{"op": "add", "path": "/spec/template/spec/containers/0/readinessProbe/initialDelaySeconds", "value":10 }]'` +```bash +kubectl create -f https://raw.githubusercontent.com/keycloak/keycloak-quickstarts/12.0.4/kubernetes-examples/keycloak.yaml +kubectl rollout status deploy/keycloak +``` -Then, we need to configure it and create two users: +Then, you need to configure it and create two users: - User1 credentials: `user1/password` Email: user1@solo.io @@ -320,6 +614,8 @@ Then, we need to configure it and create two users: - User2 credentials: `user2/password` Email: user2@example.com +Let's create the users: + ```bash # Get Keycloak URL and token KEYCLOAK_URL=http://$(kubectl get service keycloak -o jsonpath='{.status.loadBalancer.ingress[0].ip}'):8080/auth @@ -344,24 +640,33 @@ curl -H "Authorization: Bearer ${KEYCLOAK_TOKEN}" -X POST -H "Content-Type: appl curl -H "Authorization: Bearer ${KEYCLOAK_TOKEN}" -X POST -H "Content-Type: application/json" -d '{"username": "user2", "email": "user2@example.com", "enabled": true, "attributes": {"group": "users"}, "credentials": [{"type": "password", "value": "password", "temporary": false}]}' $KEYCLOAK_URL/admin/realms/master/users ``` +> **Note:** If you get a *Not Authorized* error, please, re-run this command and continue from the command started to fail: + +``` +KEYCLOAK_TOKEN=$(curl -d "client_id=admin-cli" -d "username=admin" -d "password=admin" -d "grant_type=password" "$KEYCLOAK_URL/realms/master/protocol/openid-connect/token" | jq -r .access_token) +``` + + + + The architecture looks like this now: ![Bookinfo with OIDC](images/bookinfo-oidc.png) -The next step is to configure the authentication in the Virtual Service. For this we will have to create a Kubernetes Secret that contains the OIDC secret: +The next step is to configure the authentication in the Virtual Service. For this, you will have to create a Kubernetes Secret that contains the OIDC secret: ```bash -glooctl create secret oauth --namespace gloo-system --name keycloak-oauth --client-secret ${secret} +glooctl create secret oauth --namespace gloo-system --name oauth --client-secret ${secret} ``` -Then we will create an AuthConfig, which is a Gloo Edge CRD that contains authentication information: +Then you will create an AuthConfig, which is a Gloo Edge CRD that contains authentication information: ```bash kubectl apply -f - < Notice that this only applies to a specific route, because it is configured within a **matcher** ```bash kubectl apply -f - < Notice that the requests for static content also count since they share the route `/`. +> `/productpage` +> `/static/` ``` -Username should only contain letters +/opt/google/chrome/chrome $(glooctl proxy url --port https)/productpage ``` -## Lab 3: Data Transformation +## Lab 4: Web Application Firewall (WAF) -In this section we will explore how to transform requests using Gloo Edge. +A web application firewall (WAF) protects web applications by monitoring, filtering and blocking potentially harmful traffic and attacks that can overtake or exploit them. -### Response Transformation +Gloo Edge Enterprise includes the ability to enable the ModSecurity Web Application Firewall for any incoming and outgoing HTTP connections. -The following example demonstrates how to modify a response using Gloo Edge. We are going to return a basic html page when the response code is 429 (rate limited). +Let's update the Virtual Service to restrict requests with huge payloads to avoid *Large Payload Post DDoS Attacks*. The intention of this DDoS attack is abuse of the amount of memory used to decode the payload bringing the server down. ```bash kubectl apply -f - <

Too many Requests!

Try again after 10 seconds

{% else %}{{ body() }}{% endif %}' -#--------------------------------------------------- - domains: - - '*' + SecRequestBodyLimit 1 + SecRequestBodyLimitAction Reject +#------------------------------------------------------ routes: + - matchers: + - prefix: /not-secured + delegateAction: + selector: + namespaces: + - team1 + labels: + application-owner: team1 - matchers: - prefix: / + options: + extauth: + configRef: + name: oauth + namespace: gloo-system routeAction: - multi: - destinations: - - weight: 5 - destination: - upstream: - name: bookinfo-productpage-9080 - namespace: gloo-system - - weight: 5 - destination: - upstream: - name: bookinfo-beta-productpage-9080 - namespace: gloo-system + single: + upstream: + name: bookinfo-productpage-9080 + namespace: gloo-system EOF ``` -Refreshing your browser a couple times, you should be able to see a styled HTML page indicating that you reached the limit. +The rule means that the request body size cannot be greater than 1KB. -## Lab 4: Delegation +Run following command to test it: -Gloo Edge provides a feature referred to as delegation. Delegation allows a complete routing configuration to be assembled from separate config objects. The root config object delegates responsibility to other objects, forming a tree of config objects. The tree always has a Virtual Service as its root, which delegates to any number of Route Tables. Route Tables can further delegate to other Route Tables. +``` +curl -k $(glooctl proxy url --port https)/not-secured/post -d "Reminderare you sure this is a huge payload???" -H "Content-Type: application/xml" +``` -Use cases for delegation include: +You should get the following error message: -- Allowing multiple tenants to own add, remove, and update routes without requiring shared access to the root-level Virtual Service -- Sharing route configuration between Virtual Services -- Simplifying blue-green routing configurations by swapping the target Route Table for a delegated route. -- Simplifying very large routing configurations for a single Virtual Service -- Restricting ownership of routing configuration for a tenant to a subset of the whole Virtual Service. +``` +Payload sizes above 1KB not allowed +``` + + +#### WAF Blocks well-known harmful User-Agents + +Another kind of attack is done by some recognized User-Agents. -Let's rewrite our Virtual Service to delegate the routing to a Route Table: +In this step, you will block `scammer`: ```bash kubectl apply -f - <

Too many Requests!

Try again after 10 seconds

{% else %}{{ body() }}{% endif %}' - domains: - - '*' + SecRule REQUEST_HEADERS:User-Agent "scammer" "deny,status:403,id:107,phase:1,msg:'blocked scammer'" +#------------------------------------------------------------------- routes: - matchers: - - prefix: / + - prefix: /not-secured delegateAction: - ref: - name: 'demo' - namespace: 'gloo-system' + selector: + namespaces: + - team1 + labels: + application-owner: team1 + - matchers: + - prefix: / + options: + extauth: + configRef: + name: oauth + namespace: gloo-system + routeAction: + single: + upstream: + name: bookinfo-productpage-9080 + namespace: gloo-system EOF ``` -As you can see, in this case the security options remains in the `VirtualService` (and can be managed by the infrastructure team) while the routing options are now in the `RouteTable` (and can be managed by the application team). - -## Lab 5: Observability - -### Metrics - -Gloo Edge automatically generates a Grafana dashboard for whole-cluster stats (overall request timing, aggregated response codes, etc.), and dynamically generates a more-specific dashboard for each upstream that is tracked. +The rule means a well known `scammer` User-Agent will be blocked. -Let's run the following command to allow access to the Grafana UI: +Run following command to test it: ``` -kubectl port-forward -n gloo-system svc/glooe-grafana 8001:80 +curl -k $(glooctl proxy url --port https)/not-secured/get -H "Content-Type: application/xml" -H 'User-Agent: scammer' ``` -You can now access the Grafana UI at http://localhost:8001 and login with `admin/admin`. - -You can take a look at the `Gloo -> Envoy Statistics` Dashboard that provides global statistics: - -![Grafana Envoy Statistics](images/grafana1.png) - -You can also see that Gloo is dynamically generating a Dashboard for each Upstream: - -![Grafana Upstream](images/grafana2.png) - -You can run the following command to see the default template used to generate these templates: +You should get the following error message: ``` -kubectl -n gloo-system get cm gloo-observability-config -o yaml +Blocked Scammer ``` -If you want to customize how these per-upstream dashboards look, you can provide your own template to use by writing a Grafana dashboard JSON representation to that config map key. +## Lab 5: Data Transformation -### Access Logging +In this section you will explore how to transform requests and responses using Gloo Edge. -Access logs are important to check if a system is behaving correctly and for debugging purposes. Log aggregators like Datadog and Splunk use agents deployed on the Kubernetes clusters to collect logs. +### Response Transformation -Lets first enable access logging on the gateway: +Before, it was mentioned that **httpbin** is a good tool to debug and test things out. In this section, you will use that application to easily spot the results. + +Following example demonstrates how to modify a response using Gloo Edge. You are going to return a basic html page when the response code is 429 (rate limited). ```bash kubectl apply -f - <

Too many Requests!

Try again after 1 minute

{% else %}{{ body() }}{% endif %}' +#--------------------------------------------------- + routeAction: + single: + upstream: + name: team1-httpbin-8000 + namespace: gloo-system EOF ``` -NOTE: You can safely ignore the following warning when you run the above command: +Refreshing your browser more than 5 times, you should be able to see a styled HTML page indicating that you have reached the limit you had configured before. ``` -Warning: kubectl apply should be used on resource created by either kubectl create --save-config or kubectl apply +/opt/google/chrome/chrome $(glooctl proxy url --port https)/not-secured/get ``` -Refresh your browser a couple times to generate some traffic. +### Manipulate the response when a 401 Not Authorized is returned -Check the access logs running the following command: +The following example shows how to create a transformation which modifies the body when the error code is 401 Not Authorized. -```bash -kubectl logs -n gloo-system deployment/gateway-proxy | grep '^{' | jq -``` +This transformation applies to a scenario where you use ExtAuth. Now, you will only simulate the 401. -If you refresh the browser to send additional requests until the rate limiting threshold is exceeded, then you will see both `200 OK` and `429 Too Many Requests` responses in the access logs, as in the example below. +In Gloo, you can define transformations to happen: +- In an `early` stage. Before all filters. +- In a `regular` stage. After all filters. -``` -{ - "messageType": null, - "requestId": "06c54299-de6b-463e-8035-aebd3e530cb5", - "httpMethod": "GET", - "systemTime": "2020-10-22T21:38:18.316Z", - "path": "/productpage", - "targetDuration": 31, - "protocol": "HTTP/2", - "responseFlags": "-", - "number": null, - "clientDuration": 31, - "upstreamName": "bookinfo-beta-productpage-9080_gloo-system", - "responseCode": 200 -} -{ - "httpMethod": "GET", - "systemTime": "2020-10-22T21:38:19.168Z", - "targetDuration": null, - "path": "/productpage", - "protocol": "HTTP/2", - "responseFlags": "-", - "clientDuration": 3, - "number": null, - "responseCode": 429, - "upstreamName": null, - "messageType": null, - "requestId": "494c3cc7-e476-4414-8c50-499f3619f84c" -} -``` - -These logs can now be collected by the Log aggregator agents and potentially forwarded to your favorite enterprise logging service. - -The following labs are optional. The instructor will go through them. - -## Lab 6: Advanced Authentication Workflows - -As you've seen in the previous lab, Gloo Edge supports authentication via OpenID Connect (OIDC). OIDC is an identity layer on top of the OAuth 2.0 protocol. In OAuth 2.0 flows, authentication is performed by an external Identity Provider (IdP) which, in case of success, returns an Access Token representing the user identity. The protocol does not define the contents and structure of the Access Token, which greatly reduces the portability of OAuth 2.0 implementations. - -The goal of OIDC is to address this ambiguity by additionally requiring Identity Providers to return a well-defined ID Token. OIDC ID tokens follow the JSON Web Token standard and contain specific fields that your applications can expect and handle. This standardization allows you to switch between Identity Providers – or support multiple ones at the same time – with minimal, if any, changes to your downstream services; it also allows you to consistently apply additional security measures like Role-based Access Control (RBAC) based on the identity of your users, i.e. the contents of their ID token. - -As explained above, Keycloak will return a JWT token, so we’ll use Gloo to extract some claims from this token and to create new headers corresponding to these claims. - -Finally, we’ll see how Gloo Edge RBAC rules can be created to leverage the claims contained in the JWT token. +> Notice that the response transformation is flagged as `early`. The reason is that an Auth Server returning 401 will cancel any filter or transformation designed to happen after that (`regular` stage). Therefore, you need to prepare the response transformation before the Auth Server filter happens in an `early` stage. -First of all, let's deploy a new application that returns information about the requests it receives: - -```bash -kubectl apply -f - < With the **httpbin** application, calling `/get`, you can debug the request headers. since you are transforming it to add a new header, after running: -Here is the output you should get if you refresh the web page: +``` +curl -k $(glooctl proxy url --port https)/not-secured/get -H "x-my-initial-header: Bearer this_is_my_token_for_test" +``` + +You can see the new `X-My-Final-Header` header only with the token: ``` { - "args": {}, "headers": { - "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", - "Accept-Encoding": "gzip, deflate, br", - "Accept-Language": "en-US,en;q=0.9", - "Cache-Control": "max-age=0", - "Content-Length": "0", - "Host": "172.18.1.1", - "Jwt": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJZd18zVTBoOFFkbGpsODBiSkQxUDhzbUtud0ZYYkVJZE0xZjlMU3R1N2E0In0.eyJleHAiOjE2MDc0MTkyOTMsImlhdCI6MTYwNzQxOTIzMywiYXV0aF90aW1lIjoxNjA3NDE4NzQ5LCJqdGkiOiI2NWExYzM0Ni0wNTY5LTQwNWUtYTNmZi0wOTVjZGE3MGRiYmMiLCJpc3MiOiJodHRwOi8vMTcyLjE4LjAuMjExOjgwODAvYXV0aC9yZWFsbXMvbWFzdGVyIiwiYXVkIjoiZjg3YzAzOWQtNTdiZi00NTQzLWExZjAtOGIyMmZjNmY5ZTYwIiwic3ViIjoiN2Q0N2I2YmYtOTcyYi00OGRjLWI3YjctM2U5N2NlZGM4NjM1IiwidHlwIjoiSUQiLCJhenAiOiJmODdjMDM5ZC01N2JmLTQ1NDMtYTFmMC04YjIyZmM2ZjllNjAiLCJzZXNzaW9uX3N0YXRlIjoiOGJmNjAzMTYtY2NmYi00ZWZkLWFiNDgtOTc5MmQzNTkzNzBhIiwiYXRfaGFzaCI6IkV5ZUtXbjhELWdZQlIxOWhpaEg2YXciLCJhY3IiOiIwIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJ1c2VyMSIsImVtYWlsIjoidXNlcjFAc29sby5pbyIsImdyb3VwIjoidXNlcnMifQ.nrwvo8F1jKjyQCED95gLYAvYi9TxRRDW6_Z8WC8c61WU1hHUMsHJG77G-CG0T8NwORG2cB7dlP3iu_M_e9BaONCsCZsUZUCpwV5w7ZsFxbbMy4jWSuQyd38kTnoFyMHQGxCXGI0VS02TqsAaO6oQIjwoC6Ib_6MKxsgYNrIGhp7FihO7D1rfBW-Ggvqx88INFSMCKWOft6xzYvBS6JQcDjLXMAkc4TOmTBFZkfXpepsKlDjFxW5DreaDZXv1zIUM-dG-1MRk_N5CPg_OWgnjiF4gKWTqCG8hJd__QPwo4RO7FqM5BM8o0u_lugNbgHlB-09GjO7NTZiZHiQB3HxWVw", - "Sec-Fetch-Dest": "document", - "Sec-Fetch-Mode": "navigate", - "Sec-Fetch-Site": "none", - "Sec-Fetch-User": "?1", - "Upgrade-Insecure-Requests": "1", - "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36", - "X-Envoy-Expected-Rq-Timeout-Ms": "15000", - "X-User-Id": "http://172.18.1.2:8080/auth/realms/master;7d47b6bf-972b-48dc-b7b7-3e97cedc8635" +[...] + "X-My-Final-Header": "this_is_my_token_for_test", + "X-My-Initial-Header": "Bearer this_is_my_token_for_test" }, - "origin": "192.168.149.15", - "url": "https://172.18.1.1/get" +[...] } ``` -You can see that the `Jwt` header has been added to the request while the cookie header has been removed. - -### Inspect the JWT token claims +### Extract information from the JWT token -The JWT token contains a number of claims that we can use to drive RBAC decisions and potentially to provide other input to our upstream systems. +The JWT token contains a number of claims that you can use to drive RBAC decisions and potentially to provide other input to your upstream systems. In this case, let's take a closer look at the claims that the Keycloak-generated JWT provides us. Paste the text of the `Jwt` header into the `Encoded` block at [jwt.io](https://jwt.io), and it will show you the full set of claims available. ![JWT Claims](images/jwt-claims.png) -### Extract information from the JWT token - -JWKS is a set of public keys that can be used to verify the JWT tokens. +Let's obtain those claims. -First, we need to assign the value to a variable of the keycloak master realm. +First, you need to assign the value to a variable of the keycloak master realm. ```bash KEYCLOAK_MASTER_REALM_URL=http://$(kubectl get svc keycloak -ojsonpath='{.status.loadBalancer.ingress[0].ip}'):8080/auth/realms/master ``` +Now, you can update the resources to validate the token, extract claims from the token and create new headers based on these claims. -Now, we can update the Virtual Service to validate the token, extract claims from the token and create new headers based on these claims. +Notice that you will move the `extauth` block to a global location in the resource so that it affects all routes. You want to test this with **httpbin**. ```bash kubectl apply -f - < Envoy Statistics` Dashboard that provides global statistics: + +![Grafana Envoy Statistics](images/grafana1.png) + +You can also see that Gloo is dynamically generating a Dashboard for each Upstream: + +![Grafana Upstream](images/grafana2.png) + +You can run the following command to see the default template used to generate these templates: + +``` +kubectl -n gloo-system get cm gloo-observability-config -o yaml +``` + +If you want to customize how these per-upstream dashboards look, you can provide your own template to use by writing a Grafana dashboard JSON representation to that config map key. + +### Access Logging + +Access logs are important to check if a system is behaving correctly and for debugging purposes. Log aggregators like Datadog and Splunk use agents deployed on the Kubernetes clusters to collect logs. + +Lets first enable access logging on the gateway: + +```bash +kubectl apply -f - <