Blog archive

How to Protect Amazon EKS Applications with ALB and AWS WAF

Protect applications on Amazon EKS by associating AWS WAF controls with an Application Load Balancer provisioned for Kubernetes ingress.

Published · Republished on Medium

AWSKubernetesWafSecurityAlb

Screenshot from AWS: Securing EKS Applications using ALB Integration with AWS WAF

What this control protects

The AWS Load Balancer Controller can provision an Application Load Balancer from a Kubernetes Ingress and associate a Regional AWS WAF v2 web ACL with that ALB. WAF then evaluates HTTP requests before the ALB forwards accepted traffic to the Kubernetes service.

This is one security layer, not complete application protection. Keep authentication, authorization, secure application code, pod and network controls, dependency scanning, logging, and incident response in the design.

Ownership model

Decide which system owns each resource before deployment:

  • Kubernetes and the AWS Load Balancer Controller own the ALB, listeners, target groups, and their reconciliation.
  • Infrastructure as Code should own the WAF web ACL, rules, log destination, KMS settings, and alarms.
  • The Ingress or IngressClassParams supplies the web ACL association.

Avoid manually editing controller-owned ALB resources in the AWS console. The controller can overwrite those changes during reconciliation.

Prerequisites

  • An EKS cluster with supported networking add-ons and correctly tagged or explicitly selected subnets.
  • AWS Load Balancer Controller IAM permissions configured with IRSA or EKS Pod Identity.
  • A public ACM certificate in the same Region as the ALB.
  • A Regional WAF v2 web ACL in the same Region as the ALB.
  • DNS, application health endpoint, and security groups designed before exposure.

Pin the controller chart version and review its release notes. Helm upgrades do not automatically update the controller CRDs, so include the documented CRD step in the upgrade procedure.

Step 1: Install the AWS Load Balancer Controller

Create the IAM role and Kubernetes service account by following the current EKS installation guide. The trust relationship for IRSA is cluster-specific; do not reuse it without adding the new cluster's OIDC provider.

bash

helm repo add eks https://aws.github.io/eks-charts
helm repo update eks
helm search repo eks/aws-load-balancer-controller --versions

helm upgrade --install aws-load-balancer-controller \
  eks/aws-load-balancer-controller \
  --namespace kube-system \
  --version 1.14.0 \
  --set clusterName=prod-eks-cluster \
  --set serviceAccount.create=false \
  --set serviceAccount.name=aws-load-balancer-controller \
  --set region=ap-southeast-1 \
  --set vpcId=vpc-0123456789abcdef0

The version is an explicit example pin. Confirm the current supported controller/chart combination before adopting it, then promote the tested artifact between environments.

bash

kubectl -n kube-system rollout status deployment/aws-load-balancer-controller
kubectl -n kube-system logs deployment/aws-load-balancer-controller --tail=100

Step 2: Create the web ACL conservatively

Start new managed-rule groups in Count mode against representative traffic. Review sampled requests and WAF logs, add exclusions for understood false positives, then move rules to blocking in stages.

A practical evaluation order is:

  1. Explicit allow or block rules for narrowly defined application requirements.
  2. AWS Managed Rules selected for the application and tested in Count mode.
  3. Rate-based rules with limits based on measured endpoints and client behavior.
  4. A default action, normally Allow, with application-specific block rules above it.

Rule priority determines evaluation order. WAF capacity, managed rule subscriptions, Bot Control, request volume, and logging destinations all affect cost. Model those costs before enabling the configuration across many ALBs.

Treat client IP carefully

For an internet-facing ALB associated directly with WAF, use the request origin seen by WAF unless the architecture intentionally relies on a trusted upstream proxy. A forwarded-IP configuration trusts a named header; clients can spoof it unless the upstream component overwrites and controls that header. Define fallback behavior for missing or malformed values.

Do not copy a generic “requests per minute per IP” threshold. AWS WAF rate-based rules use an evaluation window and aggregation keys. Choose both from real traffic, NAT concentration, API behavior, and acceptable abuse risk.

Step 3: Attach WAF through a secure Ingress

yaml

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: secure-app-ingress
  namespace: production
  annotations:
    alb.ingress.kubernetes.io/scheme: internet-facing
    alb.ingress.kubernetes.io/target-type: ip
    alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS":443}]'
    alb.ingress.kubernetes.io/certificate-arn: arn:aws:acm:ap-southeast-1:123456789012:certificate/11111111-2222-3333-4444-555555555555
    alb.ingress.kubernetes.io/ssl-policy: ELBSecurityPolicy-TLS13-1-2-2021-06
    alb.ingress.kubernetes.io/wafv2-acl-arn: arn:aws:wafv2:ap-southeast-1:123456789012:regional/webacl/prod-web-acl/11111111-2222-3333-4444-555555555555
    alb.ingress.kubernetes.io/healthcheck-path: /healthz
    alb.ingress.kubernetes.io/healthcheck-port: traffic-port
    alb.ingress.kubernetes.io/success-codes: "200"
    alb.ingress.kubernetes.io/tags: Environment=production,Owner=platform
spec:
  ingressClassName: alb
  rules:
    - host: app.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: frontend-service
                port:
                  number: 80

The policy allows TLS 1.3 and TLS 1.2; it does not mean every connection uses TLS 1.3. Test client compatibility before changing an existing listener policy.

Use only HTTPS listeners unless there is an explicit HTTP-to-HTTPS redirect requirement. Keep the pod transport and security-group boundary documented; TLS termination at the ALB means the backend hop is not automatically encrypted.

Be cautious with shared ALBs

alb.ingress.kubernetes.io/group.name merges multiple Ingress resources onto one ALB. Any principal that can create or modify an Ingress in that group may influence listener rules and exclusive annotations such as the WAF association. Share an IngressGroup only inside one administrative trust boundary.

For stronger governance, use IngressClassParams to constrain namespaces and pin the WAF ACL at the platform level. Do not combine group.name and load-balancer-name merely as a cost optimization; they have different reconciliation semantics, and the load-balancer name is applied only at creation.

Step 4: Verify reconciliation and association

bash

kubectl -n production describe ingress secure-app-ingress

ALB_DNS=$(kubectl -n production get ingress secure-app-ingress \
  -o jsonpath='{.status.loadBalancer.ingress[0].hostname}')

ALB_ARN=$(aws elbv2 describe-load-balancers \
  --region ap-southeast-1 \
  --query "LoadBalancers[?DNSName=='${ALB_DNS}'].LoadBalancerArn | [0]" \
  --output text)

test -n "$ALB_ARN"
test "$ALB_ARN" != "None"

aws wafv2 get-web-acl-for-resource \
  --region ap-southeast-1 \
  --resource-arn "$ALB_ARN"

Also confirm the certificate, listener policy, security groups, target health, and controller events. Send a known benign request and a safe request that matches a test rule; verify the expected allow/count/block result in WAF metrics and logs.

Logging and alerting

Enable WAF logging to a supported destination and redact sensitive fields where appropriate. Logs may contain URLs, headers, and other regulated information, so apply encryption, least privilege, retention, and access auditing.

Monitor at least:

  • AllowedRequests, BlockedRequests, and CountedRequests by web ACL and rule.
  • Sudden rate-based rule activation and managed-rule false positives.
  • ALB HTTP 4xx/5xx, target 5xx, latency, rejected connections, and unhealthy targets.
  • Controller reconciliation errors and unexpected changes to the web ACL association.

An alert on blocked requests alone is noisy. Correlate changes with baseline traffic, application errors, authentication signals, and WAF log samples.

Safe rollout and rollback

  1. Deploy rules in Count mode.
  2. Review normal traffic and false positives.
  3. Enable blocking for one rule group at a time.
  4. Watch WAF and application signals through a defined observation window.
  5. Roll back a problematic rule to Count or disable that rule group—not the entire protection layer—when possible.

Removing the WAF annotation does not necessarily disassociate the existing web ACL: current controller documentation says an absent or empty annotation leaves the ALB WAFv2 setting unchanged. To disable the association through the controller, explicitly use the documented value none, subject to your policy and change controls.

Before deleting an Ingress, understand whether the controller will also delete the ALB. Changing an IngressGroup name can create a new ALB, and an empty group is deleted even if ALB deletion protection was enabled through annotations.

Production checklist

  • Controller, chart, and CRDs are pinned and upgraded together.
  • ALB, WAF, certificate, and S3/log resources use the intended Region.
  • Kubernetes RBAC prevents untrusted IngressGroup membership.
  • Managed rules were measured in Count mode before blocking.
  • Rate limits and forwarded-IP behavior reflect the real traffic path.
  • WAF logs are encrypted, access-controlled, redacted, and retained appropriately.
  • Association, TLS, target health, allow, count, and block behavior are tested.
  • Cost alarms and a rule-level rollback procedure exist.

Further reading

Conclusion

The strongest ALB and WAF design has explicit ownership and a controlled rollout. Let Kubernetes reconcile the ALB, manage WAF policy as reviewed infrastructure, limit who can influence shared Ingress resources, validate the actual association, and move rules from counting to blocking only after observing real application traffic.