Skip to main content

Feature Implementation Cookbook: 5 Gateway API Implementations

45 min read
info

This document is a deep-dive guide for the Gateway API Adoption Guide. It compares, with YAML examples, how the 8 major features used in NGINX Ingress are implemented in each Gateway API implementation. For solution selection, comparison matrices, and the decision tree, see Section 4 of the main guide.

Overview​

This cookbook covers how to implement the following 8 features for AWS Native (LBC v3), Cilium, NGINX Gateway Fabric, Envoy Gateway, and kGateway. URL Rewrite and header manipulation are Gateway API v1 standard features, but the standard defines Core and Extended support levels per feature, and the supported scope can vary by implementation and version. Confirm that the required filter is supported in each implementation before using it.

Table 1. The eight features covered in this cookbook and their standard status

#FeatureStandard?
1Authentication (Basic Auth replacement)Per-implementation
2Rate LimitingPer-implementation
3IP Control (IP Allowlist)Per-implementation
4URL RewriteGateway API v1 standard
5Header ManipulationGateway API v1 standard
6Session Affinity (cookie-based)Per-implementation
7Request Body Size LimitPer-implementation
8Custom Error PagesPer-implementation

1. Authentication (Basic Auth replacement)​

The tabs are alternative configurations for different controllers. Prepare the corresponding versioned CRDs/controller, the production namespace, a production-gateway HTTPS listener named https in the same namespace, its TLS certificate/DNS and the api-service:8080 backend. Replace example.com IdP/host values and Secret/public-key references with the deployment's configuration. Rendering or parsing a manifest does not establish successful authentication.

The presence or regex match of an Authorization header is not authentication. JWT signature, issuer, audience and time checks, or a real authorization service's allow/deny decision, are required. The examples embed no working credentials; no deployment or authentication requests were executed.

AWS LBC 3.0.0 exposes ListenerRuleConfiguration in gateway.k8s.aws/v1beta1. On an HTTPS listener, ALB checks an RS256 JWT's signature, iss and exp, plus nbf/iat when present. This example also requires the string claim aud=api-gateway-client; use the matching claim format if the IdP emits an audience array. Verify public JWKS reachability and ALB limits in the official JWT documentation.

# auth-aws.yaml
# AWS Load Balancer Controller v3.0.0; existing HTTPS listener named https.
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: jwt-protected-route
namespace: production
spec:
parentRefs:
- name: production-gateway
sectionName: https
hostnames:
- api.example.com
rules:
- matches:
- path:
type: PathPrefix
value: /api
filters:
- type: ExtensionRef
extensionRef:
group: gateway.k8s.aws
kind: ListenerRuleConfiguration
name: api-jwt
backendRefs:
- name: api-service
port: 8080
---
apiVersion: gateway.k8s.aws/v1beta1
kind: ListenerRuleConfiguration
metadata:
name: api-jwt
namespace: production
spec:
actions:
- type: jwt-validation
jwtValidationConfig:
jwksEndpoint: https://idp.example.com/realms/production/protocol/openid-connect/certs
issuer: https://idp.example.com/realms/production
additionalClaims:
- name: aud
format: single-string
values:
- api-gateway-client

JWT validation is distinct from a browser OIDC login redirect. A valid token is forwarded as-is; this extension does not configure claimsToHeaders. Define a separate trusted identity-header/overwrite contract if the backend requires one. LBC 3.0.0 LRC contract

For the selected mechanism, verify valid credentials, missing/tampered/expired tokens or incorrect passwords, wrong issuer/audience, authorizer failure, callback routing and direct-backend access. Record applicable Accepted/ResolvedRefs status and actual backend reachability. Static schema checks do not replace these runtime acceptance results.

2. Rate Limiting​

Limitation

AWS Native (LBC v3) does not support gateway-level native rate limiting. IP-based request limiting is implemented using AWS WAF Rate-based Rules.

# Attach a WAF Rate-based Rule to the ALB (LBC v3 Gateway)
# WAF is attached via LoadBalancerConfiguration.spec.wafV2.webACL (an ARN string),
# not a Gateway annotation; the Gateway references it through
# spec.infrastructure.parametersRef.
apiVersion: gateway.k8s.aws/v1
kind: LoadBalancerConfiguration
metadata:
name: production-lb-config
namespace: production
spec:
wafV2:
webACL: arn:aws:wafv2:us-west-2:123456789012:regional/webacl/rate-limit/a1b2c3d4
---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: production-gateway
namespace: production
spec:
gatewayClassName: aws-alb
infrastructure:
parametersRef:
group: gateway.k8s.aws
kind: LoadBalancerConfiguration
name: production-lb-config
listeners:
- name: http
port: 80
protocol: HTTP

Create a WAF Rate-based Rule with ACK (AWS Controllers for Kubernetes):

Using the ACK WAFv2 controller, WAF resources can be managed declaratively as Kubernetes manifests.

Enable ACK with EKS Capabilities (recommended):

Using EKS Capabilities (GA in November 2025), the ACK controller can run as a fully AWS-managed component. Because the controller runs on AWS-managed infrastructure, no separate pod is deployed on the worker nodes.

# 1. Create the IAM Capability Role
aws iam create-role \
--role-name EKS-ACK-Capability-Role \
--assume-role-policy-document '{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": { "Service": "capabilities.eks.amazonaws.com" },
"Action": ["sts:AssumeRole", "sts:TagSession"]
}]
}'

# Attach the WAFv2 permission policy
aws iam put-role-policy \
--role-name EKS-ACK-Capability-Role \
--policy-name ACK-WAFv2-Policy \
--policy-document '{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": ["wafv2:*"],
"Resource": "*"
}]
}'

# 2. Create the ACK Capability on the EKS cluster
aws eks create-capability \
--region <REGION> \
--cluster-name my-eks-cluster \
--capability-name my-ack \
--type ACK \
--role-arn arn:aws:iam::<ACCOUNT_ID>:role/EKS-ACK-Capability-Role \
--delete-propagation-policy RETAIN

# 3. Wait until the capability is ACTIVE, then verify CRD registration
aws eks describe-capability \
--region <REGION> \
--cluster-name my-eks-cluster \
--capability-name my-ack \
--query 'capability.status' --output text
kubectl get crds | grep wafv2
Alternative: install directly with Helm (non-EKS environments)

For non-EKS environments or when the controller must be self-managed, it can be installed with Helm.

helm install ack-wafv2-controller \
oci://public.ecr.aws/aws-controllers-k8s/wafv2-chart \
--namespace ack-system \
--create-namespace \
--set aws.region=ap-northeast-2

With this approach the controller is deployed as a pod on the worker nodes, and permissions are managed via IRSA (IAM Roles for Service Accounts).

# ACK WAFv2 WebACL - Rate-based Rule definition
apiVersion: wafv2.services.k8s.aws/v1alpha1
kind: WebACL
metadata:
name: rate-limit-acl
namespace: production
spec:
name: rate-limit-acl
scope: REGIONAL
defaultAction:
allow: {}
rules:
- name: ip-rate-limit
priority: 1
action:
block: {}
statement:
rateBasedStatement:
limit: 500 # requests allowed per evaluation window (example); minimum allowed is 10
evaluationWindowSec: 300 # evaluation window: one of 60/120/300/600 seconds (default 300)
aggregateKeyType: IP # one aggregation option: aggregate by source IP
visibilityConfig:
sampledRequestsEnabled: true
cloudWatchMetricsEnabled: true
metricName: ip-rate-limit
visibilityConfig:
sampledRequestsEnabled: true
cloudWatchMetricsEnabled: true
metricName: rate-limit-acl
# Attach the created WebACL ARN to the Gateway
# After WebACL creation, find the ARN in status.ackResourceMetadata.arn:
# kubectl get webacl rate-limit-acl -n production \
# -o jsonpath='{.status.ackResourceMetadata.arn}'
apiVersion: gateway.k8s.aws/v1
kind: LoadBalancerConfiguration
metadata:
name: production-lb-config
namespace: production
spec:
wafV2:
webACL: <WebACL ARN>
---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: production-gateway
namespace: production
spec:
gatewayClassName: aws-alb
infrastructure:
parametersRef:
group: gateway.k8s.aws
kind: LoadBalancerConfiguration
name: production-lb-config
listeners:
- name: http
port: 80
protocol: HTTP
ACK WAFv2 Controller Requirements
  • The ACK WAFv2 controller requires IAM permissions such as wafv2:CreateWebACL, wafv2:UpdateWebACL, wafv2:DeleteWebACL, and wafv2:GetWebACL
  • When using EKS Capabilities: attach the WAFv2 permissions to the IAM Capability Role. The controller runs on AWS-managed infrastructure
  • When using Helm install: grant least privilege via IRSA (IAM Roles for Service Accounts) or EKS Pod Identity
  • The WebACL and the ALB must be in the same region

3. IP Control (IP Allowlist)​

# Attach WAF IP Allowlist to the ALB (LBC v3 Gateway)
apiVersion: gateway.k8s.aws/v1
kind: LoadBalancerConfiguration
metadata:
name: production-lb-config
namespace: production
spec:
wafV2:
webACL: arn:aws:wafv2:us-west-2:123456789012:regional/webacl/ip-allowlist/a1b2c3d4
---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: production-gateway
namespace: production
spec:
gatewayClassName: aws-alb
infrastructure:
parametersRef:
group: gateway.k8s.aws
kind: LoadBalancerConfiguration
name: production-lb-config
listeners:
- name: http
port: 80
protocol: HTTP

Create a WAF IP Allowlist with ACK (AWS Controllers for Kubernetes):

Using the ACK WAFv2 controller, IPSet and WebACL can be managed declaratively as Kubernetes manifests.

# 1. ACK WAFv2 IPSet - define the list of allowed IPs
apiVersion: wafv2.services.k8s.aws/v1alpha1
kind: IPSet
metadata:
name: allowed-ips
namespace: production
spec:
name: allowed-ips
scope: REGIONAL
ipAddressVersion: IPV4
addresses:
- "10.0.0.0/8" # internal VPC
- "192.168.1.0/24" # office network
- "203.0.113.100/32" # specific allowed IP
# 2. ACK WAFv2 WebACL - IPSet-based allowlist rule
# After IPSet creation, find the ARN in status.ackResourceMetadata.arn:
# kubectl get ipset allowed-ips -n production \
# -o jsonpath='{.status.ackResourceMetadata.arn}'
apiVersion: wafv2.services.k8s.aws/v1alpha1
kind: WebACL
metadata:
name: ip-allowlist-acl
namespace: production
spec:
name: ip-allowlist-acl
scope: REGIONAL
defaultAction:
block: {} # block by default, only the allowlist passes
rules:
- name: allow-trusted-ips
priority: 1
action:
allow: {}
statement:
ipSetReferenceStatement:
arn: <IPSet ARN> # ARN of the allowed-ips IPSet
visibilityConfig:
sampledRequestsEnabled: true
cloudWatchMetricsEnabled: true
metricName: allow-trusted-ips
visibilityConfig:
sampledRequestsEnabled: true
cloudWatchMetricsEnabled: true
metricName: ip-allowlist-acl
# 3. Attach the created WebACL ARN to the Gateway
# After WebACL creation, find the ARN in status.ackResourceMetadata.arn:
# kubectl get webacl ip-allowlist-acl -n production \
# -o jsonpath='{.status.ackResourceMetadata.arn}'
apiVersion: gateway.k8s.aws/v1
kind: LoadBalancerConfiguration
metadata:
name: production-lb-config
namespace: production
spec:
wafV2:
webACL: <WebACL ARN>
---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: production-gateway
namespace: production
spec:
gatewayClassName: aws-alb
infrastructure:
parametersRef:
group: gateway.k8s.aws
kind: LoadBalancerConfiguration
name: production-lb-config
listeners:
- name: http
port: 80
protocol: HTTP
ACK WAFv2 IPSet Management Tips
  • Updating the addresses field of the IPSet causes the ACK controller to automatically sync the AWS WAF IPSet
  • Combined with GitOps (ArgoCD/Flux), IP changes can be managed via PRs
  • The IPSet and WebACL must be in the same region, and wafv2:*IPSet* and wafv2:*WebACL* permissions are required (EKS Capabilities: IAM Capability Role / Helm: IRSA)

4. URL Rewrite​

Gateway API Standard

URL Rewrite is a Gateway API v1 standard feature, but the support level for prefix rewrite (ReplacePrefixMatch) and full-path rewrite can vary by implementation and version. Confirm the implementation's supported scope before use.

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: api-rewrite
namespace: production
spec:
parentRefs:
- name: production-gateway
rules:
# /api/v1/users → /users
- matches:
- path:
type: PathPrefix
value: /api/v1
filters:
- type: URLRewrite
urlRewrite:
path:
type: ReplacePrefixMatch
replacePrefixMatch: /
backendRefs:
- name: api-service
port: 8080

# /old-api/users → /v2/users
- matches:
- path:
type: PathPrefix
value: /old-api
filters:
- type: URLRewrite
urlRewrite:
path:
type: ReplacePrefixMatch
replacePrefixMatch: /v2
backendRefs:
- name: api-service-v2
port: 8080

5. Header Manipulation​

Gateway API Standard

Header manipulation is a Gateway API v1 standard feature, but the support level of request/response header modifier filters can vary by implementation and version. Confirm the implementation's supported scope before use.

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: header-manipulation
spec:
parentRefs:
- name: production-gateway
rules:
- matches:
- path:
value: /api
filters:
# Add request headers
- type: RequestHeaderModifier
requestHeaderModifier:
add:
- name: X-Custom-Header
value: "gateway-api"
- name: X-Forwarded-Proto
value: "https"
remove:
- Authorization # remove existing Authorization
# Add response headers
- type: ResponseHeaderModifier
responseHeaderModifier:
add:
- name: X-Server
value: "gateway-api"
- name: Strict-Transport-Security
value: "max-age=31536000; includeSubDomains"
backendRefs:
- name: api-service
port: 8080
# On an LBC v3 Gateway, stickiness is not configured through HTTPRoute
# annotations. Define a TargetGroupConfiguration (targetReference points to the
# backend Service) and set the target group attributes (key/value) under
# targetGroupProps.targetGroupAttributes.
apiVersion: gateway.k8s.aws/v1
kind: TargetGroupConfiguration
metadata:
name: api-service-tg-config
namespace: production
spec:
targetReference:
name: api-service
defaultConfiguration:
targetGroupAttributes:
- key: stickiness.enabled
value: "true"
- key: stickiness.type
value: lb_cookie
- key: stickiness.lb_cookie.duration_seconds
value: "3600"
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: sticky-session
namespace: production
spec:
parentRefs:
- name: production-gateway
rules:
- backendRefs:
- name: api-service
port: 8080

7. Request Body Size Limit​

Limitation

AWS WAF attached to an ALB inspects only the first 8 KiB (8,192 bytes) of the request body. The oversizeHandling: MATCH in the sizeConstraintStatement below matches (blocks) any body larger than 8 KiB regardless of the configured size threshold, so this rule is "block bodies that exceed the inspection limit," not a "10 MiB upload cap." To cap the full request body size, enforce it at a component that receives the entire request (for example, Envoy request buffering or NGINX body-size settings).

# Attach a WAF Body Size Limit Rule to the ALB (LBC v3 Gateway)
apiVersion: gateway.k8s.aws/v1
kind: LoadBalancerConfiguration
metadata:
name: production-lb-config
namespace: production
spec:
wafV2:
webACL: arn:aws:wafv2:us-west-2:123456789012:regional/webacl/body-size-limit/a1b2c3d4
---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: production-gateway
namespace: production
spec:
gatewayClassName: aws-alb
infrastructure:
parametersRef:
group: gateway.k8s.aws
kind: LoadBalancerConfiguration
name: production-lb-config
listeners:
- name: http
port: 80
protocol: HTTP

Create a WAF Body Size Rule with ACK (AWS Controllers for Kubernetes):

Using the ACK WAFv2 controller, body size limit rules can be managed declaratively as Kubernetes manifests.

# ACK WAFv2 WebACL - Body Size Limit Rule definition
apiVersion: wafv2.services.k8s.aws/v1alpha1
kind: WebACL
metadata:
name: body-size-limit-acl
namespace: production
spec:
name: body-size-limit-acl
scope: REGIONAL
defaultAction:
allow: {}
rules:
- name: block-large-body
priority: 1
action:
block: {}
statement:
sizeConstraintStatement:
fieldToMatch:
body:
oversizeHandling: MATCH # match (block) bodies larger than 8 KiB, regardless of size
comparisonOperator: GT
size: 10485760 # 10 MiB (in bytes); separate from WAF's body inspection limit
textTransformations:
- priority: 0
type: NONE
visibilityConfig:
sampledRequestsEnabled: true
cloudWatchMetricsEnabled: true
metricName: block-large-body
visibilityConfig:
sampledRequestsEnabled: true
cloudWatchMetricsEnabled: true
metricName: body-size-limit-acl
# Attach the created WebACL ARN to the Gateway
# After WebACL creation, find the ARN in status.ackResourceMetadata.arn:
# kubectl get webacl body-size-limit-acl -n production \
# -o jsonpath='{.status.ackResourceMetadata.arn}'
apiVersion: gateway.k8s.aws/v1
kind: LoadBalancerConfiguration
metadata:
name: production-lb-config
namespace: production
spec:
wafV2:
webACL: <WebACL ARN>
---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: production-gateway
namespace: production
spec:
gatewayClassName: aws-alb
infrastructure:
parametersRef:
group: gateway.k8s.aws
kind: LoadBalancerConfiguration
name: production-lb-config
listeners:
- name: http
port: 80
protocol: HTTP
Consolidate Rules into a Single WebACL

If you use IP Allowlist, Rate Limiting, and Body Size limits all together, you do not need to create separate WebACLs for each — you can consolidate multiple rules into a single WebACL, distinguished by priority. Since only one WebACL can be attached per ALB, consolidated management is essential.

When consolidating, mind the evaluation order. If an allowlist rule permits a request with action: allow, evaluation terminates at that rule and the lower-priority rate-limit and body-size rules do not run. To apply rate and body checks to allowed IPs as well, place those checks at a higher priority than the allow rule, or design the rule structure so the allow rule does not terminate evaluation. Test both abusive requests from permitted IPs and requests from denied IPs to confirm the intended ordering.

8. Custom Error Pages​

On an LBC v3 Gateway, a fixed response is not configured with the Ingress alb.ingress.kubernetes.io/actions.* annotation or a nonexistent error-503 Service. Attach a ListenerRuleConfiguration (gateway.k8s.aws/v1beta1) to the HTTPRoute via ExtensionRef and define a fixed-response action inside it. After deploying, verify the actual response code and body are returned as intended.

# LBC v3 Gateway: ListenerRuleConfiguration fixed-response action
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: error-response
namespace: production
spec:
parentRefs:
- name: production-gateway
rules:
- matches:
- path:
type: PathPrefix
value: /maintenance
filters:
- type: ExtensionRef
extensionRef:
group: gateway.k8s.aws
kind: ListenerRuleConfiguration
name: error-503
---
apiVersion: gateway.k8s.aws/v1beta1
kind: ListenerRuleConfiguration
metadata:
name: error-503
namespace: production
spec:
actions:
- type: fixed-response
fixedResponseConfig:
contentType: text/html
statusCode: "503"
messageBody: "<html><body><h1>Service Under Maintenance</h1><p>Please try again later.</p></body></html>"

References​

Official Documentation​