Skip to content

Commit 9477b15

Browse files
committed
docs: cover listing consumers and changing rate limits in rpc-api-key skill
1 parent 4ad6228 commit 9477b15

1 file changed

Lines changed: 93 additions & 5 deletions

File tree

  • spartan/.claude/skills/create-rpc-api-key

spartan/.claude/skills/create-rpc-api-key/SKILL.md

Lines changed: 93 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,31 @@
11
---
22
name: create-rpc-api-key
3-
description: Add a new public RPC API key consumer to an Aztec RPC environment (mainnet or testnet) — mint the GCP Secret Manager secret with the helper script, wire it into Kong via Terraform, apply, verify it is live, and open the PR. Use when asked to add/create an RPC API key, onboard a new RPC client/consumer, or "give <someone> a mainnet/testnet rpc key".
3+
description: Manage public RPC API key consumers for an Aztec RPC environment (mainnet or testnet) — add a new consumer end-to-end (mint the GCP Secret Manager secret, wire it into Kong via Terraform, apply, verify, open the PR), list existing consumers and their real owners, and check or change a consumer's per-minute rate limit. Use when asked to add/create an RPC API key, onboard a new RPC client/consumer, "give <someone> a mainnet/testnet rpc key", or to list/inspect/raise/lower an RPC consumer's rate limit.
44
---
55

6-
# Create a public RPC API key
6+
# Manage public RPC API keys
77

8-
Adds one API key for the public RPC gateway. A key is a value stored in GCP Secret Manager; Terraform turns it into a Kong consumer that can call every keyed route in the environment. Externally the consumer is just `clientN`; the real recipient is recorded only as a private annotation on the secret.
8+
This runbook covers the full lifecycle of a public RPC gateway consumer: **creating** a key (Steps 1–6), **listing** who has keys, and **checking / changing** a consumer's rate limit.
9+
10+
A key is a value stored in GCP Secret Manager; Terraform turns it into a Kong consumer that can call every keyed route in the environment. Externally the consumer is just `clientN`; the real recipient is recorded only as a private `client_name` annotation on the secret.
911

1012
The two RPC environments live in `spartan/terraform/deploy-rpc/environments/`:
1113

1214
| Environment | dir | secret prefix | namespace / release prefix |
1315
|---|---|---|---|
1416
| mainnet | `environments/mainnet` | `mainnet-rpc-consumer` | `mainnet-rpc` / `mainnet` |
15-
| testnet | `environments/testnet` | (check that env's `CONSUMERS` entries) | `testnet-rpc` / `testnet` |
17+
| testnet | `environments/testnet` | `testnet-rpc-consumer` | `testnet-rpc` / `testnet` |
18+
19+
The examples below use **mainnet**. For testnet, swap the dir, prefix, namespace, and context. Always read the target env's existing consumer entries and copy their exact prefix rather than assuming.
20+
21+
## Two consumer-config shapes (mainnet vs testnet differ)
1622

17-
The examples below use **mainnet**. For testnet, swap the dir, prefix, namespace, and context. Always read the target env's existing `CONSUMERS` entries and copy their exact prefix rather than assuming.
23+
The `CONSUMERS` map passed to the environment module is written differently in each env — read the target env's `main.tf` before editing:
24+
25+
- **mainnet** — an **explicit map**, one `clientN` block per consumer, each with its own `rate_limit_minute`. This is where you set an individual mainnet consumer's limit.
26+
- **testnet****generated** from a `consumer_secret_names` list via a `for` comprehension that hardcodes `rate_limit_minute = 0` for every entry. So every testnet key is unlimited by construction, and there is no per-consumer limit knob unless you break a consumer out of the loop into its own explicit entry.
27+
28+
**Rate-limit tiers.** `rate_limit_minute = 0` means **unlimited** (no rate-limit plugin is generated; see the `consumers_with_rate_limit` filter in `spartan/terraform/modules/rpc-gateway/main.tf`). A positive value generates a `rate-limiting` KongPlugin with `policy = "local"` and `limit_by = "consumer"`**`local` means the limit is enforced per Kong pod**, so the effective ceiling is `rate_limit_minute × (number of Kong gateway pods)`. When `ALLOW_ANONYMOUS = true` (testnet), keyless callers fall back to an anonymous consumer capped at `ANONYMOUS_RATE_LIMIT_MINUTE` (default 300) **per IP, per pod**; mainnet sets `ALLOW_ANONYMOUS = false` so a key is mandatory there.
1829

1930
## Before you start — access and tooling
2031

@@ -63,6 +74,17 @@ Edit `spartan/terraform/deploy-rpc/environments/<env>/main.tf` and append a `cli
6374

6475
`rate_limit_minute = 0` means unlimited (no rate-limit plugin is generated). Use a positive value only if a cap is intended.
6576

77+
**testnet variant.** testnet does not use an explicit map — it builds `CONSUMERS` from a `consumer_secret_names` list. Add the new secret name to that list instead:
78+
79+
```hcl
80+
consumer_secret_names = [
81+
"testnet-rpc-consumer-client1",
82+
"testnet-rpc-consumer-client2", # new
83+
]
84+
```
85+
86+
Every entry inherits `rate_limit_minute = 0` (unlimited) from the comprehension. To give one testnet consumer a finite cap, it cannot stay in the uniform loop — break it out into its own explicit `CONSUMERS` entry (or merge an override) with the desired `rate_limit_minute`.
87+
6688
## Step 3 — plan and verify (the guardrail)
6789

6890
```bash
@@ -138,3 +160,69 @@ It is a bearer credential — share it over a secure channel, not plain chat/ema
138160
- Header form: `x-aztec-api-key: <KEY>` against `https://canonical.mainnet.rpc.aztec-labs.com/`
139161

140162
The consumer is defined at the environment level, so the key authenticates on **every** keyed route in that environment (e.g. mainnet `v4`, `canonical`, and `v5` hosts) — one key, all routes.
163+
164+
## Listing consumers and their owners
165+
166+
The `clientN` → real-recipient mapping lives **only** in the `client_name` annotation on each GCP secret — it is never in the committed Terraform or in the cluster. To find who owns which key, list the secrets with their annotation:
167+
168+
```bash
169+
# one env
170+
gcloud secrets list --filter="name:mainnet-rpc-consumer" \
171+
--format="table(name.basename(), createTime, annotations.client_name)"
172+
173+
# every RPC consumer across all envs (mainnet, testnet, staging, testnet-prover, eth-sepolia, …)
174+
gcloud secrets list --filter="name~rpc-consumer" \
175+
--format="value(name.basename(), annotations.client_name)" | sort
176+
```
177+
178+
To find a specific person's key, grep the annotation column — the owner may not have a key at all (in which case, on testnet, they are just using the anonymous tier and "bumping" them means creating a key):
179+
180+
```bash
181+
gcloud secrets list --filter="name~rpc-consumer" --format="value(name.basename(), annotations.client_name)" | grep -i <name>
182+
```
183+
184+
Cross-check against what is actually live in the cluster (the committed `main.tf` can lag behind live — always reconcile against `origin/merge-train/spartan`, not a possibly-stale checkout):
185+
186+
```bash
187+
CTX=gke_testnet-440309_us-west1-a_aztec-gke-public
188+
kubectl --context "$CTX" -n <ns>-rpc get kongconsumer
189+
```
190+
191+
## Checking a consumer's rate limit
192+
193+
The declared limit is the consumer's `rate_limit_minute` in the env `main.tf` (`0` = unlimited). To read the **effective live** limit instead of the declared one:
194+
195+
```bash
196+
CTX=gke_testnet-440309_us-west1-a_aztec-gke-public
197+
NS=testnet-rpc
198+
199+
# does the consumer carry a rate-limit plugin annotation? (absent ⇒ unlimited)
200+
kubectl --context "$CTX" -n "$NS" get kongconsumer <release-prefix>-<key> -o jsonpath='{.metadata.annotations}'; echo
201+
202+
# the per-consumer rate-limit plugin config, if any
203+
kubectl --context "$CTX" -n "$NS" get kongplugin <release-prefix>-<key>-rpc-rate-limit -o jsonpath='{.config}'; echo
204+
205+
# the anonymous (keyless) limit, per route — testnet only
206+
kubectl --context "$CTX" -n "$NS" get kongplugin <release-prefix>-<route>-anonymous-rpc-rate-limit -o jsonpath='{.config}'; echo
207+
208+
# Kong pod count — the local policy multiplies the limit by this
209+
kubectl --context "$CTX" -n "$NS" get pods | grep kong-gateway
210+
```
211+
212+
Remember `policy = "local"`: the real ceiling is `minute × (Kong gateway pod count)`. With a single gateway pod, the number in the plugin is the number the client sees.
213+
214+
## Changing a consumer's rate limit
215+
216+
Editing `rate_limit_minute` for an existing consumer, then `plan`/`apply` exactly as in Steps 3–4. **The Step 3 guardrail still applies**: the plan must touch only that one consumer's rate-limit plugin and its `KongConsumer` annotation — nothing else. Expected plan deltas by transition:
217+
218+
| Change | Expected plan |
219+
|---|---|
220+
| `0``N` (add a cap) | `1 to add, 1 to change` — creates the rate-limit `KongPlugin`, annotates the consumer to use it |
221+
| `N``M` (both > 0) | `1 to change` — just the plugin's `minute` |
222+
| `N``0` (remove the cap) | `1 to change, 1 to destroy` — drops the plugin, removes the consumer annotation |
223+
224+
Notes:
225+
226+
- **testnet caveat**: a testnet consumer generated by the `consumer_secret_names` loop is uniformly `rate_limit_minute = 0`. You cannot raise *one* testnet consumer above/below the others without pulling it out of the loop into an explicit `CONSUMERS` entry (see the testnet variant under Step 2). If the intent is simply "more than the 300/min anonymous tier", giving them a key already makes them unlimited — no per-consumer plugin needed.
227+
- **Raising the anonymous tier** (testnet, keyless users) is a different lever: set `ANONYMOUS_RATE_LIMIT_MINUTE` on the testnet env module (default 300). This affects **every** keyless caller, not one person.
228+
- Commit message convention for a limit change is a normal `chore:`/`fix:` describing the change (not `chore: add new client`), on base `merge-train/spartan`. Do not name the private owner in the PR.

0 commit comments

Comments
 (0)