From abb60ed1a49740e689388e9f083acee8b2d64f10 Mon Sep 17 00:00:00 2001 From: slayerjain Date: Sat, 24 Oct 2026 15:38:14 +0530 Subject: [PATCH 1/7] docs: authenticate to the Kubernetes Proxy with a service-account key or a PAT The Kubernetes Proxy REST API page told callers, CI included, to trade a personal access token for the cluster shared token and send that token on every call. The shared token is the credential the proxy's own in-cluster components use, and the proxy now accepts it only on the routes those components call. - Authentication now sends a service-account key (CI and automation) or a PAT (a person) straight in the Bearer header. It explains what a key can call (per-route permissions, enforce vs. the default audit mode), how to set up a service account for CI (ITG_RUNNER / ATG_RUNNER, key expiry and rotation), the failure modes, and how long the proxy caches a validation. - The PAT -> shared-token exchange and the Console's Get Shared Token button are documented as deprecated, with the 403 the shared token now gets and how to migrate a script. - The quick start, the endpoint reference and the static-deduplication example use the key directly. Signed-off-by: slayerjain --- .../keploy-cloud/static-deduplication.md | 2 +- .../running-keploy/k8s-proxy-api.md | 174 +++++++----------- 2 files changed, 66 insertions(+), 110 deletions(-) diff --git a/versioned_docs/version-4.0.0/keploy-cloud/static-deduplication.md b/versioned_docs/version-4.0.0/keploy-cloud/static-deduplication.md index 90056c559..7b198247c 100644 --- a/versioned_docs/version-4.0.0/keploy-cloud/static-deduplication.md +++ b/versioned_docs/version-4.0.0/keploy-cloud/static-deduplication.md @@ -82,7 +82,7 @@ When driving recordings through the Kubernetes Proxy REST API, set `static_dedup ```bash curl -s -X POST "$PROXY/record/start" \ - -H "Authorization: Bearer $K8S_PROXY_SHARED_TOKEN" \ + -H "Authorization: Bearer $KEPLOY_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "namespace": "default", diff --git a/versioned_docs/version-4.0.0/running-keploy/k8s-proxy-api.md b/versioned_docs/version-4.0.0/running-keploy/k8s-proxy-api.md index 25f069d50..f1df54ce3 100644 --- a/versioned_docs/version-4.0.0/running-keploy/k8s-proxy-api.md +++ b/versioned_docs/version-4.0.0/running-keploy/k8s-proxy-api.md @@ -17,7 +17,7 @@ keywords: - live recording - auto replay - programmatic recording - - shared token + - service account --- import ProductTier from '@site/src/components/ProductTier'; @@ -96,18 +96,20 @@ Combined with capture-time static deduplication (benefit 2), this keeps the curr ## Authentication -Authenticating to the proxy is a **two-step exchange**: - -1. Create a **Personal Access Token (PAT)** in the Keploy Console. -2. `POST /get-shared-token` with that PAT to receive the cluster's **shared token**. - -Every other protected route on the proxy is then gated on the shared token sent as a Bearer header: +Every protected route takes a Keploy API key, sent as a Bearer header: ```text -Authorization: Bearer +Authorization: Bearer ``` -Only `GET /healthz`, the admission webhook `POST /mutate`, the bootstrap `POST /get-shared-token`, and the introspection `GET /shared-token/info` are unauthenticated. Every other route rejects missing or malformed headers with `401 Unauthorized`. +The key is one of two kinds. Both are `kep_`-prefixed strings, and the proxy validates both with the Keploy API server: + +| Key | Use it for | Where to create it | +| ------------------------------- | ---------------------------------------------------- | ----------------------------------------------- | +| **Service-account key** | CI/CD pipelines, operators, and other automation | Keploy Console, **Settings › Service Accounts** | +| **Personal access token (PAT)** | A person scripting against the proxy from a terminal | Keploy Console, **Settings → API Keys** | + +Only `GET /healthz`, the admission webhook `POST /mutate`, and the introspection `GET /shared-token/info` are unauthenticated. Every other route rejects a missing or malformed header with `401 Unauthorized`. ```bash # Verify the proxy is up (no auth required) @@ -115,116 +117,72 @@ curl -sf https://$PROXY/healthz # {"status":"ok"} ``` -### Why a two-step exchange? - -The PAT identifies a specific user; the shared token authorizes calls against a specific cluster. Splitting the two lets callers (CI scripts, AI agents, internal tooling) store one user-rotatable PAT and exchange it at run time without `kubectl` access to the proxy namespace or an interactive user login. The shared token is still a cluster-wide secret and can also be surfaced through existing heartbeat-backed Console/API flows, so handle it as sensitive wherever it appears. - -### 1. Issue a PAT - -In the Keploy Console, open **Settings → API Keys** and click **Create token**. PATs are 47-character strings prefixed with `kep_`. - -- The PAT must belong to the same tenant (`cid`) as the cluster the proxy is registered to and include `write` or `admin` scope. The proxy will reject cross-tenant PATs or PATs with insufficient scope with `403 Forbidden`. -- Treat the PAT like a password—it is the long-lived credential. Store it in your CI provider's secret store, not in the repo. -- A user can have multiple PATs. Revoke or rotate them from the same Console screen; revoked PATs stop working immediately. - -### 2. Exchange the PAT for the shared token +### What a key can call -There are two ways to do the exchange. Pick whichever fits your workflow—they both hit the same `POST /get-shared-token` endpoint. +Each route requires one permission, such as `record:start` or `testcase:read`. The proxy asks the API server which permissions the key's owner holds on this cluster, and, when a request names an app, on that app. -#### Option A: From the Keploy Console +- With `KEPLOY_RBAC_MODE=enforce` (`proxy.rbacMode: enforce` in the Helm chart), a key without the route's permission gets `403 Forbidden`. +- In the default audit mode, the proxy logs the missing permission and allows the call. A key that holds nothing on this cluster is checked strictly in every mode, so it can call only what its app-level grants allow. +- A key that belongs to a different organization than the proxy's cluster is refused with `403 Forbidden`. -Open the cluster's detail page in the Console. Each cluster card on the top row shows the live ingress URL; the **Get Shared Token** button sits inside that card. +### Service accounts for CI -Get Shared Token button on the Ingress URL card +A service account is a non-human identity in your organization. It holds nothing until an organization admin grants it a role, and it can never manage roles, teams, access, or other service accounts, so a leaked key cannot widen its own access. -Clicking it opens a dialog pre-filled with the cluster's ingress URL. Paste your PAT and submit. +1. **Create the account.** In the Keploy Console, open **Settings › Service Accounts** (organization admins only) and create one account per pipeline or tool. +2. **Grant it access** on the cluster, or on the apps the pipeline works with. A service account takes one or both of two roles: + - `ITG_RUNNER` runs `keploy cloud replay`. It reads the recorded test cases, mocks, schema, mappings, and auto-replay results, reads replay status, and uploads replay reports. + - `ATG_RUNNER` runs `keploy test-suite --cloud`. +3. **Create a key.** Every key expires: 90 days by default, 365 days at most. An account can hold up to 10 live keys, so you can rotate one without downtime and revoke a single consumer's key without touching the others. +4. **Store the key** in your CI provider's secret store as `KEPLOY_API_KEY`, and send it as the Bearer token: -Exchange PAT for shared token dialog +```bash +PROXY="https://your-proxy-ingress" +AUTH="Authorization: Bearer $KEPLOY_API_KEY" +``` -On success, the dialog displays the `sharedToken`, `ingressUrl`, and `deploymentType` returned by the proxy. Use the **Copy sharedToken** button to grab the token for your CI script or terminal. +Neither runner role includes starting or stopping a recording or an in-cluster replay (`record:start`, `record:stop`, `replay:start`, `replay:stop`). With `KEPLOY_RBAC_MODE=enforce`, call those routes with the key of someone who holds those permissions on the cluster, such as a cluster admin. -Successful exchange showing sharedToken, ingressUrl, deploymentType +### Personal access tokens -The PAT is held in browser memory for the lifetime of the dialog only—it's never persisted to local storage and never sent to the Keploy API server from the Console (the proxy itself does that validation server-side). +In the Keploy Console, open **Settings → API Keys** and click **Create token**. PATs are 47-character strings prefixed with `kep_`. -#### Option B: From a shell +- A PAT acts as the person who created it, with that person's permissions on the cluster, up to the scopes chosen when the token was created. +- Treat the PAT like a password. Store it in a secret store, not in the repo. +- A person can have multiple PATs. Revoke or rotate them from the same Console screen; revoked PATs stop working immediately. ```bash PROXY="https://your-proxy-ingress" PAT="kep_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" - -RESP=$(curl -sS -X POST "$PROXY/get-shared-token" \ - -H "Authorization: Bearer $PAT") - -K8S_PROXY_SHARED_TOKEN=$(echo "$RESP" | jq -r '.sharedToken') -INGRESS_URL=$(echo "$RESP" | jq -r '.ingressUrl') - -AUTH="Authorization: Bearer $K8S_PROXY_SHARED_TOKEN" +AUTH="Authorization: Bearer $PAT" ``` -A successful exchange returns: +### Authentication failure modes -```json -{ - "ingressUrl": "https://your-proxy-ingress", - "sharedToken": "3e14be232bce3e3cf6f6d58f284b6eb88db3280c54d93a7951e5000c6bbe3e9a", - "deploymentType": "saas", - "tokenId": "408aaecaba458939", - "issuedAt": 1777883713 -} -``` - -- `sharedToken`—use this on every subsequent call as `Authorization: Bearer `. -- `ingressUrl`—echoes back the address the proxy was installed with, so a script can derive every other URL from one bootstrap call. -- `deploymentType`—either `"saas"` for the hosted control plane or `"self-hosted"` for self-hosted installs. -- `tokenId`—a short non-secret identifier that changes whenever the proxy regenerates `sharedToken`. Cache it alongside the token; use `GET /shared-token/info` to check whether your cached value is still current. -- `issuedAt`—unix timestamp when the proxy minted this `sharedToken`. - -### Shared token lifetime and rotation - -The shared token is generated **fresh in process memory at every Pod startup** with `crypto/rand` (32 bytes, hex-encoded). It is _not_ stored in a Kubernetes Secret, _not_ persisted to disk, and _not_ stable across restarts. Concretely, the token rotates whenever: - -- the proxy Pod restarts (CrashLoop, eviction, node drain, OOM), -- `helm upgrade` rolls the proxy Deployment, -- you call `POST /proxy/update` and the new image becomes ready. +| Status | When | +| ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `401` | Missing or empty `Authorization` header, or the key is invalid, revoked, or expired. | +| `403` | The key belongs to a different organization than the proxy's cluster, it lacks the route's permission under `enforce`, or it is the cluster shared token (see below). | +| `502` | The proxy could not reach the API server to validate the key (transient, retry). | +| `503` | The proxy is still booting and has not authenticated to the API server yet (retry). | -Treat the shared token as **scoped to one running process**, not "the lifetime of the install." A CI script that exchanges the PAT once and caches the result for hours will start getting `401 Invalid token` the moment the proxy is rolled. +The proxy validates a key with the API server and caches the answer in memory for five minutes, so a busy pipeline does not pay a validation round trip on every call. Revoking a key, or disabling its service account, takes effect on the API server at once and on the proxy when that cached answer expires. While the proxy cannot reach the API server, it keeps serving a key it has already validated for up to 30 minutes, so a short API server outage does not stop a running pipeline. The proxy never writes the key to disk or to its logs. -#### Detecting a rotated token +### Deprecated: the shared-token exchange -`GET /shared-token/info` returns only the current `tokenId` and `issuedAt`—never the token itself, so it is safe to call without the shared token. +Earlier releases had callers exchange a PAT for the cluster's **shared token** (`POST /get-shared-token`, or the **Get Shared Token** button in the Console) and send that token on every call. That flow is deprecated. -```bash -curl -sS "$PROXY/shared-token/info" -# {"tokenId":"408aaecaba458939","issuedAt":1777883713} -``` - -Recommended pattern in CI: cache `(tokenId, sharedToken)` together when you exchange, and before every long-running operation (or as a quick guard at the start of every step) hit `/shared-token/info` to compare. If `tokenId` differs from your cached value, re-exchange the PAT. +The shared token is the credential the proxy's own in-cluster components authenticate with: the node agents and the external replay runner. It names no user and every one of those components holds it, so the proxy accepts it only on the routes those components call, in every RBAC mode. On every other route, including `/record/start`, `/record/stop`, `/test/start`, and `/deployments`, it gets `403 Forbidden`: -```bash -CACHED_TOKEN_ID="$(jq -r '.tokenId' < ~/.cache/keploy-proxy.json)" -CURRENT_TOKEN_ID="$(curl -sS "$PROXY/shared-token/info" | jq -r '.tokenId')" -if [ "$CACHED_TOKEN_ID" != "$CURRENT_TOKEN_ID" ]; then - # proxy was restarted, re-exchange the PAT - RESP=$(curl -sS -X POST "$PROXY/get-shared-token" -H "Authorization: Bearer $PAT") - echo "$RESP" > ~/.cache/keploy-proxy.json -fi +```json +{ + "success": false, + "message": "the cluster shared token may not call this route; call it with a service-account key or a personal access token", + "required_permission": "record:start" +} ``` -If you instead see a sudden `401 Invalid token` on a previously working `sharedToken`, that's the same signal: the proxy was rolled. Re-exchange the PAT and retry once. - -### Exchange failure modes - -| Status | When | -| ------ | --------------------------------------------------------------------------------------------------------------------------- | -| `401` | Missing/empty `Authorization` header, or the PAT is invalid, revoked, or expired. | -| `403` | The PAT is valid but belongs to a different tenant than this proxy's cluster, or it does not include `write`/`admin` scope. | -| `502` | The proxy could not reach the API server to validate the PAT (transient—retry). | -| `503` | The proxy is still booting and has not authenticated to the API server yet (retry). | - -Under the hood, `POST /get-shared-token` calls `POST /cluster/pat/validate` on the API server (using the proxy's own cluster JWT) to verify the PAT, then returns the cached shared token only on success. The PAT is never echoed back, never stored on the proxy, and never logged in cleartext. - -> The shared token is cluster-wide, not per-user. The PAT-exchange path authenticates who is allowed to bootstrap it, but the returned shared token should still be treated as a sensitive cluster credential. +To migrate a script, send the service-account key or PAT itself as the Bearer token, and remove the exchange step along with any `tokenId` caching built around it. A key does not rotate when the proxy restarts, so a CI job no longer needs to re-exchange after a `helm upgrade` or a proxy self-update. --- @@ -236,17 +194,15 @@ The golden path: authenticate, pick a Deployment, start a recording, stream its ```bash PROXY="https://k8s-proxy.example.com" # ingressUrl from Helm install -PAT="kep_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +PAT="kep_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" # your PAT (see Authentication above) NS="default" DEPLOY="orders-api" -# One-time PAT → shared-token exchange (see Authentication above) -K8S_PROXY_SHARED_TOKEN=$(curl -sS -X POST "$PROXY/get-shared-token" \ - -H "Authorization: Bearer $PAT" | jq -r '.sharedToken') - -AUTH="Authorization: Bearer $K8S_PROXY_SHARED_TOKEN" +AUTH="Authorization: Bearer $PAT" ``` +The PAT goes straight into the Bearer header; there is no exchange step. Starting and stopping a recording needs `record:start` and `record:stop` on the cluster (see [What a key can call](#what-a-key-can-call)). + ### 2. Discover target Deployments ```bash @@ -363,7 +319,7 @@ Attach an `auto_replay_config` to `POST /record/start` to automatically replay f ## Endpoint reference -All paths are relative to the proxy base URL. Unless noted, every route requires `Authorization: Bearer `. +All paths are relative to the proxy base URL. Unless noted, every route requires `Authorization: Bearer `, a service-account key or a PAT (see [Authentication](#authentication)). ### Health and admission @@ -372,14 +328,14 @@ All paths are relative to the proxy base URL. Unless noted, every route requires | `GET` | `/healthz` | No | Liveness probe. Returns `{"status":"ok"}`. | | `POST` | `/mutate` | No | Kubernetes MutatingAdmissionWebhook endpoint. Do not call directly. | -### Bootstrap +### Shared-token exchange (deprecated) -| Method | Path | Auth | Description | -| ------ | -------------------- | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `POST` | `/get-shared-token` | `Authorization: Bearer ` | Exchange a Personal Access Token for the proxy's shared token. See [Exchange the PAT for the shared token](#2-exchange-the-pat-for-the-shared-token) for details. | -| `GET` | `/shared-token/info` | None | Return only the current `tokenId` and `issuedAt`; never the token itself. Use this to detect when the proxy has rotated the shared token (Pod restart, `helm upgrade`, self-update). | +| Method | Path | Auth | Description | +| ------ | -------------------- | ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `POST` | `/get-shared-token` | `Authorization: Bearer ` | Deprecated. Returns the cluster shared token, which the proxy accepts only on its in-cluster components' routes. See [Deprecated: the shared-token exchange](#deprecated-the-shared-token-exchange). | +| `GET` | `/shared-token/info` | None | Return only the current `tokenId` and `issuedAt` of the cluster shared token; never the token itself. | -`POST /get-shared-token` gates on a PAT instead of the shared token because the caller does not yet have one. `GET /shared-token/info` is unauthenticated because the response leaks no credential material. Every other route below requires `Authorization: Bearer `. +`GET /shared-token/info` is unauthenticated because the response leaks no credential material. Every other route below requires `Authorization: Bearer `. ### Deployments @@ -449,7 +405,7 @@ Omit `selectedTests` to replay every set. ### API-server-compatible data routes -These routes are all mounted under `/k8s-proxy` and are served directly by the proxy. Direct proxy calls use the proxy shared token; calls routed through the API server use normal Console/API-server authentication and role checks. +These routes are all mounted under `/k8s-proxy` and are served directly by the proxy. Direct proxy calls use a service-account key or a PAT; calls routed through the API server use normal Console/API-server authentication and role checks. #### Health From 9cf7f78a3cb31688c3a8e9848beea3cbac8d968b Mon Sep 17 00:00:00 2001 From: slayerjain Date: Sat, 24 Oct 2026 15:47:11 +0530 Subject: [PATCH 2/7] docs: name the Console's Account menu and its Generate new token button for keys Signed-off-by: slayerjain --- .../version-4.0.0/running-keploy/k8s-proxy-api.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/versioned_docs/version-4.0.0/running-keploy/k8s-proxy-api.md b/versioned_docs/version-4.0.0/running-keploy/k8s-proxy-api.md index f1df54ce3..04c4f1292 100644 --- a/versioned_docs/version-4.0.0/running-keploy/k8s-proxy-api.md +++ b/versioned_docs/version-4.0.0/running-keploy/k8s-proxy-api.md @@ -104,10 +104,10 @@ Authorization: Bearer The key is one of two kinds. Both are `kep_`-prefixed strings, and the proxy validates both with the Keploy API server: -| Key | Use it for | Where to create it | -| ------------------------------- | ---------------------------------------------------- | ----------------------------------------------- | -| **Service-account key** | CI/CD pipelines, operators, and other automation | Keploy Console, **Settings › Service Accounts** | -| **Personal access token (PAT)** | A person scripting against the proxy from a terminal | Keploy Console, **Settings → API Keys** | +| Key | Use it for | Where to create it | +| ------------------------------- | ---------------------------------------------------- | ---------------------------------------------- | +| **Service-account key** | CI/CD pipelines, operators, and other automation | Keploy Console, **Account › Service Accounts** | +| **Personal access token (PAT)** | A person scripting against the proxy from a terminal | Keploy Console, **Account › API Keys** | Only `GET /healthz`, the admission webhook `POST /mutate`, and the introspection `GET /shared-token/info` are unauthenticated. Every other route rejects a missing or malformed header with `401 Unauthorized`. @@ -129,7 +129,7 @@ Each route requires one permission, such as `record:start` or `testcase:read`. T A service account is a non-human identity in your organization. It holds nothing until an organization admin grants it a role, and it can never manage roles, teams, access, or other service accounts, so a leaked key cannot widen its own access. -1. **Create the account.** In the Keploy Console, open **Settings › Service Accounts** (organization admins only) and create one account per pipeline or tool. +1. **Create the account.** In the Keploy Console, open **Account › Service Accounts** (organization admins only) and create one account per pipeline or tool. 2. **Grant it access** on the cluster, or on the apps the pipeline works with. A service account takes one or both of two roles: - `ITG_RUNNER` runs `keploy cloud replay`. It reads the recorded test cases, mocks, schema, mappings, and auto-replay results, reads replay status, and uploads replay reports. - `ATG_RUNNER` runs `keploy test-suite --cloud`. @@ -145,7 +145,7 @@ Neither runner role includes starting or stopping a recording or an in-cluster r ### Personal access tokens -In the Keploy Console, open **Settings → API Keys** and click **Create token**. PATs are 47-character strings prefixed with `kep_`. +In the Keploy Console, open **Account › API Keys** and click **Generate new token**. PATs are 47-character strings prefixed with `kep_`. - A PAT acts as the person who created it, with that person's permissions on the cluster, up to the scopes chosen when the token was created. - Treat the PAT like a password. Store it in a secret store, not in the repo. From 0151de8af1b9677eb1882d7a0451ded12bb0ef5e Mon Sep 17 00:00:00 2001 From: slayerjain Date: Sat, 24 Oct 2026 16:13:11 +0530 Subject: [PATCH 3/7] docs: say CI uses ITG_RUNNER or ATG_RUNNER keys and recordings a PAT; the shared-token cut is hard The Kubernetes Proxy page told a CI job that starts and stops recordings to borrow a cluster admin's key under enforce. CI does not start or stop recordings: `keploy cloud replay` runs on a service-account key bound to ITG_RUNNER and `keploy test-suite --cloud` on one bound to ATG_RUNNER, and neither calls /record/start, /record/stop, /test/start or /test/stop. Starting or stopping a recording or an in-cluster replay through the proxy API is a person's action, done with their own PAT, which needs the record or replay permissions on the cluster under enforce. The deprecated shared-token section now says plainly that the restriction is a hard cut: no grace period and no log-only release, so a script still sending the shared token gets 403 on those routes from the upgrade on, in every RBAC mode. Scripts can switch first, since the proxy already accepts keys and PATs on every route. Signed-off-by: slayerjain --- .../version-4.0.0/running-keploy/k8s-proxy-api.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/versioned_docs/version-4.0.0/running-keploy/k8s-proxy-api.md b/versioned_docs/version-4.0.0/running-keploy/k8s-proxy-api.md index 04c4f1292..3a4f14088 100644 --- a/versioned_docs/version-4.0.0/running-keploy/k8s-proxy-api.md +++ b/versioned_docs/version-4.0.0/running-keploy/k8s-proxy-api.md @@ -141,7 +141,7 @@ PROXY="https://your-proxy-ingress" AUTH="Authorization: Bearer $KEPLOY_API_KEY" ``` -Neither runner role includes starting or stopping a recording or an in-cluster replay (`record:start`, `record:stop`, `replay:start`, `replay:stop`). With `KEPLOY_RBAC_MODE=enforce`, call those routes with the key of someone who holds those permissions on the cluster, such as a cluster admin. +Starting or stopping a recording or an in-cluster replay through the proxy API (`/record/start`, `/record/stop`, `/test/start`, `/test/stop`) is a person's action, not a CI job's: `keploy cloud replay` and `keploy test-suite --cloud` never call those routes. Call them with your own [personal access token](#personal-access-tokens). With `KEPLOY_RBAC_MODE=enforce`, its owner needs `record:start` and `record:stop`, or `replay:start` and `replay:stop`, on the cluster. ### Personal access tokens @@ -182,7 +182,9 @@ The shared token is the credential the proxy's own in-cluster components authent } ``` -To migrate a script, send the service-account key or PAT itself as the Bearer token, and remove the exchange step along with any `tokenId` caching built around it. A key does not rotate when the proxy restarts, so a CI job no longer needs to re-exchange after a `helm upgrade` or a proxy self-update. +This is a hard cut. There is no grace period and no release that only logs the shared token's use: once the proxy is upgraded to a release with this restriction, a script that still sends the shared token gets the `403` above on those routes, whatever the RBAC mode. Migrate first. The proxy already accepts service-account keys and PATs on every route, so your scripts can switch before the upgrade. + +To migrate a script, send the service-account key or PAT itself as the Bearer token, and remove the exchange step along with any `tokenId` caching built around it. A CI job uses a service-account key bound to `ITG_RUNNER` for `keploy cloud replay`, or to `ATG_RUNNER` for `keploy test-suite --cloud` (see [Service accounts for CI](#service-accounts-for-ci)); a person starting or stopping a recording or replay uses their own PAT. A key does not rotate when the proxy restarts, so a CI job no longer needs to re-exchange after a `helm upgrade` or a proxy self-update. --- From 7f7fd08f7b260c68c56e9bc576b763c6209a6dae Mon Sep 17 00:00:00 2001 From: slayerjain Date: Sat, 24 Oct 2026 16:35:00 +0530 Subject: [PATCH 4/7] =?UTF-8?q?docs:=20point=20PAT=20steps=20at=20the=20Co?= =?UTF-8?q?nsole's=20Account=20=E2=80=BA=20API=20Keys=20menu?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit smart-set-agent.md, k8s-proxy-llm-workflow.md and cloud-replay.md still sent readers to Settings → API Keys. The Console lists API Keys under its Account menu, with a Generate new token button; say so, in the words k8s-proxy-api.md already uses. Signed-off-by: slayerjain --- versioned_docs/version-4.0.0/keploy-cloud/cloud-replay.md | 2 +- versioned_docs/version-4.0.0/keploy-cloud/smart-set-agent.md | 4 ++-- .../version-4.0.0/quickstart/k8s-proxy-llm-workflow.md | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/versioned_docs/version-4.0.0/keploy-cloud/cloud-replay.md b/versioned_docs/version-4.0.0/keploy-cloud/cloud-replay.md index 99da19606..e5d27ce05 100644 --- a/versioned_docs/version-4.0.0/keploy-cloud/cloud-replay.md +++ b/versioned_docs/version-4.0.0/keploy-cloud/cloud-replay.md @@ -140,7 +140,7 @@ The two are related but conceptually distinct: [`--k8s-proxy-url`](#--k8s-proxy- | API key (PAT) | `--api-key` | `KEPLOY_API_KEY` | — | | Turn the mode on _(optional)_ | `--k8s-proxy-auth` | `KEPLOY_K8S_PROXY_AUTH=true` | `cloud.k8sProxyAuth: true` | -Only two inputs are required — a proxy URL to reach and a PAT to authenticate. **Setting the proxy URL enables the mode automatically**, so `--k8s-proxy-auth` is optional; pass it (or `KEPLOY_K8S_PROXY_AUTH=true`) only to be explicit. Generate the PAT from **Settings → API Keys** in the dashboard. +Only two inputs are required — a proxy URL to reach and a PAT to authenticate. **Setting the proxy URL enables the mode automatically**, so `--k8s-proxy-auth` is optional; pass it (or `KEPLOY_K8S_PROXY_AUTH=true`) only to be explicit. Generate the PAT in the Keploy Console: open **Account › API Keys** and click **Generate new token**. ```bash export KEPLOY_API_KEY=kep_xxxxxxxx diff --git a/versioned_docs/version-4.0.0/keploy-cloud/smart-set-agent.md b/versioned_docs/version-4.0.0/keploy-cloud/smart-set-agent.md index 0f242b60f..1d1fe0f98 100644 --- a/versioned_docs/version-4.0.0/keploy-cloud/smart-set-agent.md +++ b/versioned_docs/version-4.0.0/keploy-cloud/smart-set-agent.md @@ -38,7 +38,7 @@ The agent always stops at a **verified branch** and reports back. Merging to `ma ## Before you start - Keploy on the Enterprise plan, with smart test sets enabled on the app (`EnableSmartTestSet=true`), and the app already [recording in your cluster](/docs/quickstart/k8s-proxy). -- A **Keploy PAT** — Dashboard → Settings → API Keys. Copy the `kep_...` value (shown only once). +- A **Keploy PAT** — in the Keploy Console, open **Account › API Keys** and click **Generate new token**. Copy the `kep_...` value (shown only once). - A **Skills-aware editor**: Cursor (cursor-agent CLI / Cursor IDE), Claude Code, Windsurf, Antigravity, or any agent that loads `.../skills//SKILL.md` on demand. Older `.cursorrules` / `.windsurfrules` always-on rules files work too but bill the full skill on every editor interaction — Skills are the modern, on-demand path and what this page recommends. - **Run the agent from inside the app's git repository.** The skill inspects your `git` commit message and `git diff` to decide whether a failing case is an **intentional contract change** (update the test data) or a **regression** (fix the code). Outside a repo it can't tell them apart and will conservatively treat the change as a regression and refuse to edit the test. @@ -75,7 +75,7 @@ All MCP-aware editors accept the exact same JSON config; only the config file pa Two things to swap for your environment: - **`url`** — `https://api.keploy.io/client/v1/mcp` is the hosted Keploy endpoint. For **self-hosted** deployments use your api-server's URL (commonly `http://localhost:8083/client/v1/mcp` for a local dev install). -- **`kep_...`** — paste your PAT from Dashboard → Settings → API Keys (shown only once on creation; regenerate if you've lost it). +- **`kep_...`** — paste your PAT from the Keploy Console, **Account › API Keys** (shown only once on creation; generate a new one if you've lost it). Fully quit and reopen your editor after editing the config — MCP clients only re-read config on startup. diff --git a/versioned_docs/version-4.0.0/quickstart/k8s-proxy-llm-workflow.md b/versioned_docs/version-4.0.0/quickstart/k8s-proxy-llm-workflow.md index 407f82b15..198db2c83 100644 --- a/versioned_docs/version-4.0.0/quickstart/k8s-proxy-llm-workflow.md +++ b/versioned_docs/version-4.0.0/quickstart/k8s-proxy-llm-workflow.md @@ -51,7 +51,7 @@ This page picks up after two one-time setups are already done: the application i ## Before you start -- A **Keploy PAT** — Dashboard → Settings → API Keys. Copy the `kep_...` value (shown only once). +- A **Keploy PAT** — in the Keploy Console, open **Account › API Keys** and click **Generate new token**. Copy the `kep_...` value (shown only once). - A **Skills-aware editor**: Cursor (cursor-agent CLI / Cursor IDE), Claude Code, Windsurf, Antigravity, or any agent that loads `.../skills//SKILL.md` on demand. Older `.cursorrules` / `.windsurfrules` always-on rules files work too but bill the full ~9k-token playbook on every editor interaction — Skills are the modern, on-demand path and what this page recommends. --- @@ -87,7 +87,7 @@ All MCP-aware editors accept the exact same JSON config; only the config file pa Two things to swap for your environment: - **`url`** — `https://api.keploy.io/client/v1/mcp` is the hosted Keploy endpoint. For **self-hosted** deployments use your api-server's URL (commonly `http://localhost:8083/client/v1/mcp` for a local dev install). -- **`kep_...`** — paste your PAT from Dashboard → Settings → API Keys (shown only once on creation; regenerate if you've lost it). +- **`kep_...`** — paste your PAT from the Keploy Console, **Account › API Keys** (shown only once on creation; generate a new one if you've lost it). Fully quit and reopen your editor after editing the config — MCP clients only re-read config on startup. From 93d23a8333eeae0703811a079c6703019355c2ae Mon Sep 17 00:00:00 2001 From: slayerjain Date: Sat, 24 Oct 2026 16:49:26 +0530 Subject: [PATCH 5/7] docs: call the Console's shared-token button by its label, Get Agent Token The deprecated-exchange note named a "Get Shared Token" button. The Console labels that button "Get Agent Token", so a reader looking for it found nothing. Signed-off-by: slayerjain --- versioned_docs/version-4.0.0/running-keploy/k8s-proxy-api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/versioned_docs/version-4.0.0/running-keploy/k8s-proxy-api.md b/versioned_docs/version-4.0.0/running-keploy/k8s-proxy-api.md index 3a4f14088..221cb3b50 100644 --- a/versioned_docs/version-4.0.0/running-keploy/k8s-proxy-api.md +++ b/versioned_docs/version-4.0.0/running-keploy/k8s-proxy-api.md @@ -170,7 +170,7 @@ The proxy validates a key with the API server and caches the answer in memory fo ### Deprecated: the shared-token exchange -Earlier releases had callers exchange a PAT for the cluster's **shared token** (`POST /get-shared-token`, or the **Get Shared Token** button in the Console) and send that token on every call. That flow is deprecated. +Earlier releases had callers exchange a PAT for the cluster's **shared token** (`POST /get-shared-token`, or the **Get Agent Token** button in the Console) and send that token on every call. That flow is deprecated. The shared token is the credential the proxy's own in-cluster components authenticate with: the node agents and the external replay runner. It names no user and every one of those components holds it, so the proxy accepts it only on the routes those components call, in every RBAC mode. On every other route, including `/record/start`, `/record/stop`, `/test/start`, and `/deployments`, it gets `403 Forbidden`: From ecf3679ea66d348dfffca4299926b6a98fe32985 Mon Sep 17 00:00:00 2001 From: slayerjain Date: Sat, 24 Oct 2026 17:30:16 +0530 Subject: [PATCH 6/7] docs: show the shared token's 403 with its MACHINE_CREDENTIAL_REFUSED code The proxy's refusal of the cluster shared token now carries "code": "MACHINE_CREDENTIAL_REFUSED" and a message that names the route class. Quote that body, say a script can branch on the code instead of the message, and say that /kms/decrypt is the one key-material route the token may still call, and only over the proxy's own loopback listener. Signed-off-by: slayerjain --- versioned_docs/version-4.0.0/running-keploy/k8s-proxy-api.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/versioned_docs/version-4.0.0/running-keploy/k8s-proxy-api.md b/versioned_docs/version-4.0.0/running-keploy/k8s-proxy-api.md index 221cb3b50..07cdd8466 100644 --- a/versioned_docs/version-4.0.0/running-keploy/k8s-proxy-api.md +++ b/versioned_docs/version-4.0.0/running-keploy/k8s-proxy-api.md @@ -177,11 +177,14 @@ The shared token is the credential the proxy's own in-cluster components authent ```json { "success": false, - "message": "the cluster shared token may not call this route; call it with a service-account key or a personal access token", + "code": "MACHINE_CREDENTIAL_REFUSED", + "message": "the cluster shared token may not call this route: it is a user route, and the shared token may call only the routes the in-cluster agents and replay runners use; call it with a service-account key or a personal access token", "required_permission": "record:start" } ``` +A script can tell this refusal from a key that lacks a permission by its `code`, `MACHINE_CREDENTIAL_REFUSED`; `required_permission` names the route's permission. `POST /kms/decrypt` is the one key-material route the shared token may still call, and only from the proxy itself, over its loopback listener, where the in-cluster replay decrypts. Over the network it gets the same `403` and code, with a message that says so. + This is a hard cut. There is no grace period and no release that only logs the shared token's use: once the proxy is upgraded to a release with this restriction, a script that still sends the shared token gets the `403` above on those routes, whatever the RBAC mode. Migrate first. The proxy already accepts service-account keys and PATs on every route, so your scripts can switch before the upgrade. To migrate a script, send the service-account key or PAT itself as the Bearer token, and remove the exchange step along with any `tokenId` caching built around it. A CI job uses a service-account key bound to `ITG_RUNNER` for `keploy cloud replay`, or to `ATG_RUNNER` for `keploy test-suite --cloud` (see [Service accounts for CI](#service-accounts-for-ci)); a person starting or stopping a recording or replay uses their own PAT. A key does not rotate when the proxy restarts, so a CI job no longer needs to re-exchange after a `helm upgrade` or a proxy self-update. From 0b92b92851d226f5df2b6ce3bf18eba3ba7557b3 Mon Sep 17 00:00:00 2001 From: slayerjain Date: Sat, 24 Oct 2026 22:43:41 +0530 Subject: [PATCH 7/7] docs: say which targets execute-request reaches, and document the 503 OWNER_REPLICA_UNVERIFIED The endpoint reference now says that POST /agent/execute-request needs testgen:run, refuses the shared token, and sends a request only to an app Service the proxy manages, by its full in-cluster name, with TLS verified and redirects not followed; any other target is a 403 that says why. The failure table adds the multi-replica 503: a replica that cannot verify the replica owning a recording or replay does not forward the request, and answers OWNER_REPLICA_UNVERIFIED with a Retry-After header. Signed-off-by: slayerjain --- .../running-keploy/k8s-proxy-api.md | 41 ++++++++++--------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/versioned_docs/version-4.0.0/running-keploy/k8s-proxy-api.md b/versioned_docs/version-4.0.0/running-keploy/k8s-proxy-api.md index 07cdd8466..6094c3be0 100644 --- a/versioned_docs/version-4.0.0/running-keploy/k8s-proxy-api.md +++ b/versioned_docs/version-4.0.0/running-keploy/k8s-proxy-api.md @@ -159,12 +159,13 @@ AUTH="Authorization: Bearer $PAT" ### Authentication failure modes -| Status | When | -| ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `401` | Missing or empty `Authorization` header, or the key is invalid, revoked, or expired. | -| `403` | The key belongs to a different organization than the proxy's cluster, it lacks the route's permission under `enforce`, or it is the cluster shared token (see below). | -| `502` | The proxy could not reach the API server to validate the key (transient, retry). | -| `503` | The proxy is still booting and has not authenticated to the API server yet (retry). | +| Status | When | +| ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `401` | Missing or empty `Authorization` header, or the key is invalid, revoked, or expired. | +| `403` | The key belongs to a different organization than the proxy's cluster, it lacks the route's permission under `enforce`, or it is the cluster shared token (see below). | +| `502` | The proxy could not reach the API server to validate the key (transient, retry). | +| `503` | The proxy is still booting and has not authenticated to the API server yet (retry). | +| `503` | With more than one proxy replica: another replica owns the recording or replay, and this one could not verify it as one of the release's pods, so it did not forward the request. The body's `code` is `OWNER_REPLICA_UNVERIFIED` and a `Retry-After` header says when to retry. If it persists, check that the proxy's service account can get and list pods in its namespace. | The proxy validates a key with the API server and caches the answer in memory for five minutes, so a busy pipeline does not pay a validation round trip on every call. Revoking a key, or disabling its service account, takes effect on the API server at once and on the proxy when that cached answer expires. While the proxy cannot reach the API server, it keeps serving a key it has already validated for up to 30 minutes, so a short API server outage does not stop a running pipeline. The proxy never writes the key to disk or to its logs. @@ -479,19 +480,21 @@ These routes are all mounted under `/k8s-proxy` and are served directly by the p ### Assertion-test generator (ATG) -| Method | Path | Description | -| ------ | ------------------------------- | ---------------------------------------------------------------------------------- | -| `POST` | `/agent/run/{jobID}` | Process an ATG job. Accepts optional `?timeout=` (default `30`). | -| `POST` | `/agent/execute-request` | Execute an HTTP request through the ATG runtime (used during assertion authoring). | -| `POST` | `/agent/service-url` | Resolve a Service URL inside the cluster (used to target the app from the UI). | -| `POST` | `/agent/recordATGSandbox` | Bind to an already-running ATG sandbox recording session. | -| `POST` | `/agent/stopATGSandboxRecord` | Stop an ATG sandbox recording session. | -| `POST` | `/agent/replayATGSandbox` | Start an ATG sandbox replay session. | -| `POST` | `/agent/stopATGSandboxReplay` | Stop an ATG sandbox replay session. | -| `GET` | `/agent/ATGSandboxRecordStatus` | Fetch ATG sandbox recording status. | -| `GET` | `/agent/ATGSandboxRecordLogs` | Fetch ATG sandbox recording logs. | -| `GET` | `/agent/ATGSandboxReplayLogs` | Fetch ATG sandbox replay logs. | -| `GET` | `/agent/autoReplayLogs` | Fetch auto-replay logs. | +| Method | Path | Description | +| ------ | ------------------------------- | ------------------------------------------------------------------------------------------------------- | +| `POST` | `/agent/run/{jobID}` | Process an ATG job. Accepts optional `?timeout=` (default `30`). | +| `POST` | `/agent/execute-request` | Send an HTTP request to an app Service this proxy manages (used during assertion authoring; see below). | +| `POST` | `/agent/service-url` | Resolve a Service URL inside the cluster (used to target the app from the UI). | +| `POST` | `/agent/recordATGSandbox` | Bind to an already-running ATG sandbox recording session. | +| `POST` | `/agent/stopATGSandboxRecord` | Stop an ATG sandbox recording session. | +| `POST` | `/agent/replayATGSandbox` | Start an ATG sandbox replay session. | +| `POST` | `/agent/stopATGSandboxReplay` | Stop an ATG sandbox replay session. | +| `GET` | `/agent/ATGSandboxRecordStatus` | Fetch ATG sandbox recording status. | +| `GET` | `/agent/ATGSandboxRecordLogs` | Fetch ATG sandbox recording logs. | +| `GET` | `/agent/ATGSandboxReplayLogs` | Fetch ATG sandbox replay logs. | +| `GET` | `/agent/autoReplayLogs` | Fetch auto-replay logs. | + +`POST /agent/execute-request` needs `testgen:run`, and the shared token is refused there. It sends the request only to an app the proxy manages: a Service by its full in-cluster name, `..svc.cluster.local`, in a namespace the proxy may record (not the proxy's own), that selects pods, on one of its TCP ports. The proxy verifies the target's TLS certificate, does not follow redirects (a `3xx` comes back as the answer), and does not route the request through an egress proxy. Any other target, such as an IP address, an out-of-cluster host, a short Service name, or a URL that carries credentials, gets `403 Forbidden` with an `error` that says why. When the proxy cannot look the Service up in the cluster, it answers `503`; retry. ### Proxy self-management