Skip to main content
Calico Cloud documentation

Tutorial

note

This feature is tech preview. Tech preview features may be subject to significant changes before they become GA.

This tutorial takes you from a running Ingress Gateway to a blocked attack in about five minutes.

Prerequisites​

Block an attack in five minutes​

  1. Deploy a test application. Create an httpbin namespace with a deployment and service to act as the backend:

    apiVersion: v1
    kind: Namespace
    metadata:
    name: httpbin
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
    name: httpbin
    namespace: httpbin
    spec:
    replicas: 1
    selector:
    matchLabels:
    app: httpbin
    template:
    metadata:
    labels:
    app: httpbin
    spec:
    containers:
    - name: httpbin
    image: kong/httpbin:0.1.0
    ports:
    - containerPort: 80
    ---
    apiVersion: v1
    kind: Service
    metadata:
    name: httpbin
    namespace: httpbin
    spec:
    selector:
    app: httpbin
    ports:
    - protocol: TCP
    port: 80
    targetPort: 80

    Apply it, then wait for the pod to be ready:

    kubectl wait --for=condition=Ready pod -l app=httpbin -n httpbin --timeout=60s
  2. Create a Gateway and HTTPRoute:

    apiVersion: gateway.networking.k8s.io/v1
    kind: Gateway
    metadata:
    name: httpbin-gateway
    namespace: httpbin
    spec:
    gatewayClassName: tigera-gateway-class
    listeners:
    - name: http
    protocol: HTTP
    port: 9080
    ---
    apiVersion: gateway.networking.k8s.io/v1
    kind: HTTPRoute
    metadata:
    name: httpbin-route
    namespace: httpbin
    spec:
    parentRefs:
    - name: httpbin-gateway
    rules:
    - matches:
    - path:
    type: PathPrefix
    value: /
    backendRefs:
    - name: httpbin
    port: 80
    note

    Use port 9080, not 8080. Port 8080 is reserved by the Calico L7 log collector inside the Envoy proxy pod.

    Apply it, then wait for the Gateway to be accepted:

    kubectl wait --for=condition=Accepted gateway/httpbin-gateway -n httpbin --timeout=60s

    At this point Accepted only means Gateway passed validation. Let's now wait for the Envoy proxy Deployment that Ingress Gateway creates:

    kubectl wait --for=condition=Available deployment \
    -l gateway.envoyproxy.io/owning-gateway-name=httpbin-gateway \
    -n httpbin --timeout=180s
  3. Confirm traffic flows. Find the Envoy service the Ingress Gateway created for this Gateway (the service lands in the Gateway's own namespace, httpbin):

    export GATEWAY_SVC=$(kubectl get svc -n httpbin \
    -o jsonpath='{.items[?(@.metadata.labels.gateway\.envoyproxy\.io/owning-gateway-name=="httpbin-gateway")].metadata.name}')

    Send a test request from inside the cluster:

    kubectl run curl-test -n httpbin --rm -it --restart=Never --image=curlimages/curl -- \
    curl -s -o /dev/null -w "%{http_code}" \
    http://$GATEWAY_SVC.httpbin.svc.cluster.local:9080/get
    200

    Traffic is flowing through the Gateway with no WAF protection yet.

  4. Apply a GlobalWAFPolicy to set the cluster-wide baseline. Create the singleton default policy with the OWASP Core Rule Set enabled in Block mode:

    tip

    This tutorial uses Block mode immediately so you can see blocking in action. Most production rollouts start in Detect mode to baseline traffic before switching. See Merge order and enforcement for the tradeoffs.

    apiVersion: applicationlayer.projectcalico.org/v3
    kind: GlobalWAFPolicy
    metadata:
    name: default
    spec:
    defaultAction: Block
    coreRuleSet:
    state: Enabled
    paranoiaLevel: 1

    This defines the global posture. It does not attach WAF to any traffic yet — that requires a namespace-scoped WAFPolicy with targetRefs.

  5. Attach WAF to the Gateway. Create a WAFPolicy in the httpbin namespace targeting the Gateway:

    apiVersion: applicationlayer.projectcalico.org/v3
    kind: WAFPolicy
    metadata:
    name: httpbin-waf
    namespace: httpbin
    spec:
    targetRefs:
    - group: gateway.networking.k8s.io
    kind: Gateway
    name: httpbin-gateway

    The reconciler merges the GlobalWAFPolicy baseline with this WAFPolicy, generates an ordered ModSecurity ruleset, and programs it into Envoy Gateway as an EnvoyExtensionPolicy. Check that the policy was accepted and programmed:

    kubectl get wafpolicy httpbin-waf -n httpbin \
    -o jsonpath='{range .status.conditions[*]}{.type}={.status} ({.reason}){"\n"}{end}'
    Licensed=True (LicenseValid)
    Accepted=True (Accepted)
    Validated=Unknown (NotEvaluated)
    Programmed=True (ConfigurationApplied)
    Ready=True (Ready)

    All four executable conditions (Licensed, Accepted, Programmed, Ready) must be True before WAF is active. Validated=Unknown (NotEvaluated) is expected when no WAFValidationPolicy is in scope. If Programmed=False, check the condition message — a common cause is the WASM image not being reachable (see Troubleshooting). Allow a few seconds for Envoy to load the WASM filter before running the tests below.

  6. Test WAF blocking. A normal request still passes, but a SQL injection probe and an XSS probe are both blocked:

    # Normal request — expect 200
    kubectl run curl-ok -n httpbin --rm -it --restart=Never --image=curlimages/curl -- \
    curl -s -o /dev/null -w "%{http_code}" \
    http://$GATEWAY_SVC.httpbin.svc.cluster.local:9080/get

    # SQL injection probe — expect 403
    kubectl run curl-sqli -n httpbin --rm -it --restart=Never --image=curlimages/curl -- \
    curl -s -o /dev/null -w "%{http_code}" \
    "http://$GATEWAY_SVC.httpbin.svc.cluster.local:9080/get?id=1%27%20OR%20%271%27%3D%271"

    # XSS probe — expect 403
    kubectl run curl-xss -n httpbin --rm -it --restart=Never --image=curlimages/curl -- \
    curl -s -o /dev/null -w "%{http_code}" \
    "http://$GATEWAY_SVC.httpbin.svc.cluster.local:9080/get?q=%3Cscript%3Ealert(1)%3C%2Fscript%3E"

    The Coraza WASM filter, running inside Envoy, evaluated each request against the OWASP Core Rule Set. Core Rule Set rule group 942xxx detects SQL injection; group 941xxx detects cross-site scripting. Both attacks are blocked before the request reaches the application.

Clean up​

kubectl delete namespace httpbin
kubectl delete globalwafpolicies default

Troubleshoot​

If a WAFPolicy you attach to a route later shows Programmed=False with reason WASMUnavailable, the Coraza WASM image could not be pulled. Private registries require a pull secret to be configured on the GatewayAPI CR. See Troubleshooting for diagnosis steps and the pull-secret configuration.

Next steps​