You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
docs(k8s-proxy): document Console + browser widget for PAT exchange (#853)
* docs(k8s-proxy): document Console + browser widget for PAT exchange
The Authentication section now lists three ways to swap a PAT for the
shared token:
- Option A: the Keploy Console "Get Shared Token" dialog on the cluster
detail page (with screenshots).
- Option B: an embedded SharedTokenExchanger widget so readers can fire
the exchange against their own proxy without leaving the page.
- Option C: the original curl path, kept for CI scripts.
This makes the bootstrap flow feel like a documented product surface
rather than a curl-only one, and lets readers verify the call before
trusting it in CI.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Yash Khare <khareyash05@gmail.com>
* chore: apply prettier formatting
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Yash Khare <khareyash05@gmail.com>
* chore: fix vale lint errors
Add sharedToken/deploymentType/ingressUrl/cleartext to vale accept list
and remove spaces around em-dashes to satisfy Google.EmDash rule.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Yash Khare <khareyash05@gmail.com>
* chore: rephrase deploymentType bullet to avoid Google.EmDash false positive
Signed-off-by: Yash Khare <khareyash05@gmail.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Yash Khare <khareyash05@gmail.com>
* chore: remove unused content from k8s-proxy-api docs
Signed-off-by: Yash Khare <khareyash05@gmail.com>
* chore: remove unused SharedTokenExchanger component
The widget is no longer referenced from any doc after the inline
"Try it right here" section was removed from k8s-proxy-api.md.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Yash Khare <khareyash05@gmail.com>
* docs(k8s-proxy): correct PAT exchange docs
Signed-off-by: Asish Kumar <officialasishkumar@gmail.com>
* docs(k8s-proxy): document /shared-token/info and rotation behavior
Builds on f9af5de (which already corrected the misleading "stable for
the lifetime of the Helm release" claim) by:
- Adding the new GET /shared-token/info introspection endpoint to the
endpoint reference table and the unauthenticated-routes list.
- Surfacing tokenId + issuedAt in the example /get-shared-token
response so callers can see what to cache.
- Adding a "Shared token lifetime and rotation" section that lists the
actual rotation triggers and a "Detecting a rotated token" section
with a CI-side cache-and-revalidate pattern.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Yash Khare <khareyash05@gmail.com>
* chore: update docs
Signed-off-by: Yash Khare <khareyash05@gmail.com>
* chore: fix vale Google.Quotes lint error
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Yash Khare <khareyash05@gmail.com>
* docs: clarify k8s proxy PAT scope requirements
Signed-off-by: Asish Kumar <officialasishkumar@gmail.com>
---------
Signed-off-by: Yash Khare <khareyash05@gmail.com>
Signed-off-by: Asish Kumar <officialasishkumar@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Asish Kumar <officialasishkumar@gmail.com>
Co-authored-by: Asish Kumar <87874775+officialasishkumar@users.noreply.github.com>
@@ -98,112 +98,155 @@ Combined with capture-time static deduplication (benefit 2), this keeps the curr
98
98
99
99
## Authentication
100
100
101
-
:::info In development
102
-
The authentication flow is currently in development.
103
-
:::
101
+
Authenticating to the proxy is a **two-step exchange**:
104
102
105
-
Every protected proxy endpoint requires the cluster **shared token**. Send it as a Bearer token:
103
+
1. Create a **Personal Access Token (PAT)** in the Keploy Console.
104
+
2.`POST /get-shared-token` with that PAT to receive the cluster's **shared token**.
105
+
106
+
Every other protected route on the proxy is then gated on the shared token sent as a Bearer header:
106
107
107
108
```text
108
109
Authorization: Bearer <K8S_PROXY_SHARED_TOKEN>
109
110
```
110
111
111
-
Only `GET /healthz` and the admission webhook `POST /mutate` are unauthenticated. Every other route rejects missing or malformed headers with `401 Unauthorized`.
112
+
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`.
112
113
113
114
```bash
114
115
# Verify the proxy is up (no auth required)
115
116
curl -sf https://$PROXY/healthz
116
117
# {"status":"ok"}
117
118
```
118
119
119
-
### How the token is provisioned
120
+
### Why a two-step exchange?
120
121
121
-
The shared token is generated **at Helm install time** and stored as a Kubernetes Secret named `<release>-shared-token` in the proxy's namespace. The chart's pre-render step uses Helm's `randAlphaNum 48` to produce the value on the very first install and a `lookup` + `helm.sh/resource-policy: keep` annotation to preserve it across upgrades, so the token is **stable for the lifetime of the release**—Pod restarts and chart upgrades do not rotate it.
122
+
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.
122
123
123
-
The k8s-proxy Deployment and the per-node DaemonSet both mount the Secret as the `KEPLOY_SHARED_TOKEN` env var via `secretKeyRef`. On startup the proxy reports the value to the Keploy API server in its first heartbeat (`POST /cluster/status`) so the Console can display it under the cluster's app entries.
124
+
### 1. Issue a PAT
124
125
125
-
For local/dev runs without a Secret, if `KEPLOY_SHARED_TOKEN` is unset the proxy falls back to generating a random 32-byte value via `crypto/rand` (hex-encoded). This fallback is fresh on every restart and is**not** the path used in any Helm-managed deployment.
126
+
In the Keploy Console, open **Settings → API Keys**and click**Create token**. PATs are 47-character strings prefixed with `kep_`.
126
127
127
-
### Retrieve the token
128
+
- 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`.
129
+
- 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.
130
+
- A user can have multiple PATs. Revoke or rotate them from the same Console screen; revoked PATs stop working immediately.
128
131
129
-
Two equally valid paths.
132
+
### 2. Exchange the PAT for the shared token
130
133
131
-
**(a) Read it directly from the Secret** if you have `kubectl` access to the proxy namespace:
134
+
There are two ways to do the exchange. Pick whichever fits your workflow—they both hit the same `POST /get-shared-token` endpoint.
132
135
133
-
```bash
134
-
kubectl -n keploy get secret <release>-shared-token -o jsonpath='{.data.token}'| base64 -d
135
-
```
136
+
#### Option A: From the Keploy Console
137
+
138
+
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.
139
+
140
+

141
+
142
+
Clicking it opens a dialog pre-filled with the cluster's ingress URL. Paste your PAT and submit.
143
+
144
+

145
+
146
+
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.
**(b) Fetch it from the Keploy API server**, which mirrors what the proxy reported in its last heartbeat. Log in once to obtain a user JWT, then look up the proxy app for the Deployment you want to drive:
150
+
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).
`GET /cluster/getApps` returns the same `sharedToken` field for every proxy-managed app in your organization in a single response, which is convenient when you want to script across many Deployments at once.
167
+
A successful exchange returns:
159
168
160
-
> The proxy shared token is cluster-wide, not per-user. The API server still uses normal user JWT/cookie authentication on its own routes (including `/cluster/getApp`). The token is sticky across Pod restarts and chart upgrades, so callers can cache it for the lifetime of the Helm release.
-`sharedToken`—use this on every subsequent call as `Authorization: Bearer <sharedToken>`.
180
+
-`ingressUrl`—echoes back the address the proxy was installed with, so a script can derive every other URL from one bootstrap call.
181
+
-`deploymentType`—either `"saas"` for the hosted control plane or `"self-hosted"` for self-hosted installs.
182
+
-`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.
183
+
-`issuedAt`—unix timestamp when the proxy minted this `sharedToken`.
163
184
164
-
##Response format
185
+
### Shared token lifetime and rotation
165
186
166
-
Handlers return JSON with `application/json` on success. Validation failures usually return `{"error": "..."}` with a 4xx status; shared-token auth failures return `{"success": false, "message": "Unauthorized: ..."}`. A handful of endpoints stream newline-delimited JSON instead - they are called out explicitly below.
187
+
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:
167
188
168
-
```js
169
-
// Successful record start (200)
170
-
{ "record":"started", "id":"default-orders-api" }
189
+
- the proxy Pod restarts (CrashLoop, eviction, node drain, OOM),
190
+
-`helm upgrade` rolls the proxy Deployment,
191
+
- you call `POST /proxy/update` and the new image becomes ready.
171
192
172
-
// Validation error (400)
173
-
{ "error":"namespace and deployment are required" }
193
+
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.
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.
if [ "$CACHED_TOKEN_ID"!="$CURRENT_TOKEN_ID" ];then
210
+
# proxy was restarted, re-exchange the PAT
211
+
RESP=$(curl -sS -X POST "$PROXY/get-shared-token" -H "Authorization: Bearer $PAT")
212
+
echo"$RESP">~/.cache/keploy-proxy.json
213
+
fi
180
214
```
181
215
182
-
### Error status codes
216
+
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.
| 401 | Missing or invalid `Authorization: Bearer` header |
188
-
| 403 | Request touches a namespace outside `watchNamespace`, or image repo mismatch on `/proxy/update`|
189
-
| 404 | Recording/replay session ID not found, or deployment/config does not exist |
190
-
| 405 | Wrong HTTP method for the route |
191
-
| 500 | Kubernetes API error, storage backend unavailable, or unexpected server error |
192
-
| 503 | Kubernetes client or self-discovery not initialised (proxy is still starting or missing RBAC) |
227
+
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.
228
+
229
+
> 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.
193
230
194
231
---
195
232
196
233
## Quick start: Trigger and watch a live recording
197
234
198
-
The golden path: pick a Deployment, start a recording, stream its status, and stop it when you have the traffic you need.
235
+
The golden path: authenticate, pick a Deployment, start a recording, stream its status, and stop it when you have the traffic you need.
199
236
200
-
### 1. Set up variables
237
+
### 1. Set up variables and authenticate
201
238
202
239
```bash
203
240
PROXY="https://k8s-proxy.example.com"# ingressUrl from Helm install
|`POST`|`/get-shared-token`|`Authorization: Bearer <PAT>`| 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. |
382
+
|`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). |
383
+
384
+
`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 <K8S_PROXY_SHARED_TOKEN>`.
0 commit comments