Skip to main content
Version: 2.8.0

GraphQL Query Rate Limiting

Overview

This policy is an example of how to implement rate limiting for GraphQL queries using the Classifier.

Configuration

This policy is based on the Rate Limiting blueprint. It contains a Classifier that extracts the userID claim from a JWT token in the request's authorization header and then rate limit unique users based on the extracted user_id Flow Label; todo-service.prod.svc.cluster.local is selected as the target service for this policy.

tip

Classification rules can be written based on HTTP requests, and scheduler priorities can be defined based on Flow Labels by live previewing them first using introspection APIs.

The below values.yaml file can be generated by following the steps in the Installation section.

# yaml-language-server: $schema=../../../../../../blueprints/rate-limiting/base/gen/definitions.json
policy:
policy_name: "graphql-rate-limiting"
rate_limiter:
bucket_capacity: 40
fill_amount: 2
selectors:
- control_point: ingress
service: todo-service.svc.cluster.local
parameters:
label_key: user_id
interval: 1s
resources:
flow_control:
classifiers:
- selectors:
- control_point: ingress
service: todo-service.svc.cluster.local
rego:
labels:
user_id:
telemetry: true
module: |
package graphql_example
import future.keywords.if
query_ast := graphql.parse_query(input.parsed_body.query)
claims := payload if {
io.jwt.verify_hs256(bearer_token, "secret")
[_, payload, _] := io.jwt.decode(bearer_token)
}
bearer_token := t if {
v := input.attributes.request.http.headers.authorization
startswith(v, "Bearer ")
t := substring(v, count("Bearer "), -1)
}
queryIsCreateTodo if {
some operation
walk(query_ast, [_, operation])
operation.Name == "createTodo"
count(operation.SelectionSet) > 0
some selection
walk(operation.SelectionSet, [_, selection])
selection.Name == "createTodo"
}
user_id := u if {
queryIsCreateTodo
u := claims.userID
}

Generated Policy

apiVersion: fluxninja.com/v1alpha1
kind: Policy
metadata:
labels:
fluxninja.com/validate: "true"
name: graphql-rate-limiting
spec:
circuit:
components:
- flow_control:
rate_limiter:
in_ports:
bucket_capacity:
constant_signal:
value: 40
fill_amount:
constant_signal:
value: 2
parameters:
interval: 1s
label_key: user_id
selectors:
- control_point: ingress
service: todo-service.svc.cluster.local
evaluation_interval: 1s
resources:
flow_control:
classifiers:
- rego:
labels:
user_id:
telemetry: true
module: |
package graphql_example
import future.keywords.if
query_ast := graphql.parse_query(input.parsed_body.query)
claims := payload if {
io.jwt.verify_hs256(bearer_token, "secret")
[_, payload, _] := io.jwt.decode(bearer_token)
}
bearer_token := t if {
v := input.attributes.request.http.headers.authorization
startswith(v, "Bearer ")
t := substring(v, count("Bearer "), -1)
}
queryIsCreateTodo if {
some operation
walk(query_ast, [_, operation])
operation.Name == "createTodo"
count(operation.SelectionSet) > 0
some selection
walk(operation.SelectionSet, [_, selection])
selection.Name == "createTodo"
}
user_id := u if {
queryIsCreateTodo
u := claims.userID
}
selectors:
- control_point: ingress
service: todo-service.svc.cluster.local

info

Circuit Diagram for this policy.

For example, if the mutation query is as follows

mutation createTodo {
createTodo(input: { text: "todo" }) {
user {
id
}
text
done
}
}

Without diving deep into how Rego works, the source section mentioned in this use-case does the following:

  1. Parse the query
  2. Check if the mutation query is createTodo
  3. Verify the JWT token with a secret key secret (only for demonstration purposes)
  4. Decode the JWT token and extract the userID from the claims
  5. Assign the value of userID to the exported variable userID in Rego source

From there on, the Classifier rule assigns the value of the exported variable userID in Rego source to user_id flow label, effectively creating a label user_id:1. This label is used by the Rate Limiter component in the policy to limit the createTodo mutation query to 2 requests per second, with a burst capacity of 40 requests for each userID.

Installation

Generate a values file specific to the policy. This can be achieved using the command provided below.

aperturectl blueprints values --name=rate-limiting/base --version=v2.8.0 --output-file=values.yaml

Adjust the values to match the application requirements. Use the following command to generate the policy.

aperturectl blueprints generate --name=rate-limiting/base --values-file=values.yaml --output-dir=policy-gen --version=v2.8.0

Apply the policy using the aperturectl CLI or kubectl.

Pass the --kube flag with aperturectl to directly apply the generated policy on a Kubernetes cluster in the namespace where the Aperture Controller is installed.

aperturectl apply policy --file=policy-gen/policies/rate-limiting.yaml --kube 

Policy in Action

In this example, the traffic generator is configured to generate 50 requests per second for 2-minutes. When loading the above policy in the playground, you can observe that it accepts no more than 2 requests per second at any given time, and rejects the rest of the requests.

GraphQL Status Rate Limiting