dev-auth: add 'get' command; rewrite AGENTS.md dev-auth as quick start + gotchas
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
369c7a21ad
commit
26b677154b
2 files changed
+75
-18
No files matched your search
@@ -174,21 +174,52 @@ move to the next. Suggested order: Traces, Logs, Metrics, Search, Trends, Setup.
|
|||||||
|
|
||||||
## Local / agent development without login blocking
|
## Local / agent development without login blocking
|
||||||
|
|
||||||
Uses the go-auth device grant (RFC 8628); reference: `https://users.k6n.net/dev-auth.md`.
|
### Quick start (agents: do this first)
|
||||||
Config is CRD-driven (`https://users.k6n.net/crds.md`), see `k8s/auth-dev.yaml`:
|
|
||||||
an `AuthClient` `dev-console` with `spec.audience: [https://otel.k6n.net]`
|
|
||||||
(audience entries must be absolute URLs - `otel-debug` was rejected and briefly
|
|
||||||
made the AuthServer `READY False`), plus `AuthServer.spec.deviceFlow`. The otel
|
|
||||||
server accepts that audience via `OTEL_OIDC_AUDIENCE` (`k8s/deployment.yaml`).
|
|
||||||
|
|
||||||
- **Browser (`npm run dev`)**: `src/dev/device-auth.ts` is imported only under
|
```sh
|
||||||
`import.meta.env.DEV`. With no session it shows a "Dev sign-in" link + code,
|
node scripts/dev-auth.mjs get /api/issues # signs in if needed, then GETs
|
||||||
polls, then reloads. `/dev-auth/*` is proxied by `vite.config.ts` to the
|
```
|
||||||
issuer's `/api/auth/*` (override the issuer with `AUTH_DEV_BASE`).
|
|
||||||
- **Terminal / agents**: `node scripts/dev-auth.mjs login|token|whoami|forget`.
|
If not signed in it prints `Approve in a browser: https://users.k6n.net/device?user_code=XXXX-XXXX`.
|
||||||
Refresh token lives in `~/.go-auth/dev/` (0700/0600). Use
|
**Stop and give the human that link**, then wait - the script polls (~10 min
|
||||||
`curl -H "Authorization: Bearer $(node scripts/dev-auth.mjs token)" ...`.
|
window) and continues on its own. Refresh token is stored in `~/.go-auth/dev/`
|
||||||
`GO_AUTH_TOKEN` is an explicit-token escape hatch; never store it.
|
(0700/0600), so this happens once, not per command. Other commands:
|
||||||
- Never commit tokens, never persist them in a production build, never log them.
|
`login | token | whoami | forget`. For raw curl:
|
||||||
- Apply with `kubectl apply --dry-run=server --server-side --field-manager=otel-dev-auth -f k8s/auth-dev.yaml`
|
`curl -H "Authorization: Bearer $(node scripts/dev-auth.mjs token)" https://otel.k6n.net/api/...`.
|
||||||
first; check `kubectl get authserver -n ecommerce` stays `READY True`.
|
Env: `OTEL_URL`, `AUTH_DEV_BASE`, `AUTH_DEVICE_CLIENT_ID` (default `dev-console`),
|
||||||
|
`GO_AUTH_TOKEN` (explicit-token escape hatch, never store).
|
||||||
|
|
||||||
|
Browser (`npm run dev`): with no session a "Dev sign-in" banner shows the same
|
||||||
|
link + code and reloads once approved. Code is `src/dev/device-auth.ts`, loaded
|
||||||
|
only under `import.meta.env.DEV`; `/dev-auth/*` is proxied by `vite.config.ts`
|
||||||
|
to the issuer's `/api/auth/*`.
|
||||||
|
|
||||||
|
### Gotchas (each cost a failed attempt)
|
||||||
|
|
||||||
|
- **Request encoding:** `/api/auth/device` and `/api/auth/device/token` take
|
||||||
|
**form-encoded** bodies (RFC 8628); only `/api/auth/device/refresh` takes JSON
|
||||||
|
(`{"refresh_token": ...}`). JSON to the first two gives a fast `invalid_grant`
|
||||||
|
that looks like a denied approval but is a malformed request.
|
||||||
|
- **401 `token not issued for this client`:** the otel server rejects the
|
||||||
|
token's `aud`. It must contain `otel-debug` or `OTEL_OIDC_AUDIENCE`
|
||||||
|
(`https://otel.k6n.net`, set in `k8s/deployment.yaml`). If it 401s, the
|
||||||
|
deployed server predates that setting - redeploy with `build.sh`.
|
||||||
|
- **Never probe the issuer or auth CRDs with ad-hoc curl/kubectl** unless the
|
||||||
|
user has said to; the harness blocks it. The script above is the sanctioned path.
|
||||||
|
|
||||||
|
### CRD setup (already applied; reference)
|
||||||
|
|
||||||
|
Reference docs: `https://users.k6n.net/dev-auth.md`, `https://users.k6n.net/crds.md`.
|
||||||
|
`k8s/auth-dev.yaml` defines an `AuthClient` `dev-console` (`redirectURIs: []`
|
||||||
|
is required even for device-only clients; `spec.audience: [https://otel.k6n.net]`)
|
||||||
|
plus `AuthServer.spec.deviceFlow`.
|
||||||
|
|
||||||
|
- `audience` entries **must be absolute URLs**. `otel-debug` was rejected by the
|
||||||
|
operator and flipped the AuthServer to `READY False` until fixed.
|
||||||
|
`--dry-run=server` does **not** catch this; after applying, always check
|
||||||
|
`kubectl get authserver -n ecommerce` (READY True) and
|
||||||
|
`kubectl describe authserver auth -n ecommerce` (events).
|
||||||
|
- Apply: `kubectl apply --server-side --field-manager=otel-dev-auth -f k8s/auth-dev.yaml`
|
||||||
|
(a non-fatal "spec.issuer: Required value" warning for the partial AuthServer
|
||||||
|
apply is expected).
|
||||||
|
- Never commit tokens, persist them in a production build, or log them.
|
||||||
+27
-1
@@ -7,9 +7,12 @@
|
|||||||
// node scripts/dev-auth.mjs token print a fresh access token
|
// node scripts/dev-auth.mjs token print a fresh access token
|
||||||
// node scripts/dev-auth.mjs whoami print the token claims
|
// node scripts/dev-auth.mjs whoami print the token claims
|
||||||
// node scripts/dev-auth.mjs forget delete the stored refresh token
|
// node scripts/dev-auth.mjs forget delete the stored refresh token
|
||||||
|
// node scripts/dev-auth.mjs get PATH authenticated GET against the otel API
|
||||||
|
// (signs in first if needed), e.g. get /api/issues
|
||||||
//
|
//
|
||||||
// Env: AUTH_DEV_BASE (issuer, default https://users.k6n.net),
|
// Env: AUTH_DEV_BASE (issuer, default https://users.k6n.net),
|
||||||
// AUTH_DEVICE_CLIENT_ID (default dev-console),
|
// AUTH_DEVICE_CLIENT_ID (default dev-console),
|
||||||
|
// OTEL_URL (API for `get`, default https://otel.k6n.net),
|
||||||
// GO_AUTH_TOKEN (explicit token escape hatch; `token` prints it as-is).
|
// GO_AUTH_TOKEN (explicit token escape hatch; `token` prints it as-is).
|
||||||
import { chmod, mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
import { chmod, mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
||||||
import { homedir } from "node:os";
|
import { homedir } from "node:os";
|
||||||
@@ -74,7 +77,30 @@ async function token() {
|
|||||||
return body.access_token;
|
return body.access_token;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const API = (process.env.OTEL_URL ?? "https://otel.k6n.net").replace(/\/$/, "");
|
||||||
|
|
||||||
|
async function haveSession() {
|
||||||
|
try {
|
||||||
|
await token();
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Authenticated GET. Signs in first when there is no working session, so an
|
||||||
|
// agent can just run `get /api/issues` and ask the human to approve the link.
|
||||||
|
async function get(path) {
|
||||||
|
if (!path?.startsWith("/")) throw new Error("usage: dev-auth.mjs get /api/...");
|
||||||
|
if (!(await haveSession())) await login();
|
||||||
|
const res = await fetch(`${API}${path}`, { headers: { Authorization: `Bearer ${await token()}` } });
|
||||||
|
const text = await res.text();
|
||||||
|
if (!res.ok) throw new Error(`HTTP ${res.status} ${text.slice(0, 300)}`);
|
||||||
|
console.log(text);
|
||||||
|
}
|
||||||
|
|
||||||
const cmds = {
|
const cmds = {
|
||||||
|
get: () => get(process.argv[3]),
|
||||||
login,
|
login,
|
||||||
token: async () => console.log(await token()),
|
token: async () => console.log(await token()),
|
||||||
whoami: async () => {
|
whoami: async () => {
|
||||||
@@ -85,7 +111,7 @@ const cmds = {
|
|||||||
};
|
};
|
||||||
const cmd = cmds[process.argv[2]];
|
const cmd = cmds[process.argv[2]];
|
||||||
if (!cmd) {
|
if (!cmd) {
|
||||||
console.error("usage: dev-auth.mjs login|token|whoami|forget");
|
console.error("usage: dev-auth.mjs login|token|whoami|forget|get PATH");
|
||||||
process.exit(2);
|
process.exit(2);
|
||||||
}
|
}
|
||||||
cmd().catch((e) => {
|
cmd().catch((e) => {
|
||||||
|
|||||||
Reference in new issue
Block a user