Skip to content

Commit 5c79a73

Browse files
committed
more deepdive research into go client for kubectl compatibility
1 parent a96d1c7 commit 5c79a73

1 file changed

Lines changed: 278 additions & 31 deletions

File tree

docs/auth.md

Lines changed: 278 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -229,92 +229,339 @@ The `InClusterConfig()` flow specifically:
229229

230230
## Future Work
231231

232-
### Kubeconfig Support
232+
The following sections describe features that client-go provides which we plan to port to kubernetesjs. Each section includes the client-go reference, proposed TypeScript API, and implementation notes.
233233

234-
Parsing `~/.kube/config` (or `$KUBECONFIG`) to extract cluster endpoints, contexts, and user credentials. This would enable:
234+
### Priority 1: Kubeconfig Support
235+
236+
**client-go reference**: `clientcmd.LoadFromFile()`, `ClientConfigLoadingRules`, `DirectClientConfig`
237+
238+
Parsing `~/.kube/config` (or `$KUBECONFIG`) to extract cluster endpoints, contexts, and user credentials.
239+
240+
**Kubeconfig YAML structure** (all fields we need to parse):
241+
242+
```yaml
243+
apiVersion: v1
244+
kind: Config
245+
current-context: my-context
246+
clusters:
247+
- name: my-cluster
248+
cluster:
249+
server: https://api.example.com:6443
250+
certificate-authority: /path/to/ca.crt # or inline:
251+
certificate-authority-data: <base64-pem>
252+
insecure-skip-tls-verify: false
253+
tls-server-name: custom-sni.example.com
254+
proxy-url: socks5://proxy:1080
255+
users:
256+
- name: my-user
257+
user:
258+
token: static-bearer-token
259+
tokenFile: /path/to/token
260+
client-certificate: /path/to/cert.crt # or inline:
261+
client-certificate-data: <base64-pem>
262+
client-key: /path/to/key.pem # or inline:
263+
client-key-data: <base64-pem>
264+
username: basic-auth-user
265+
password: basic-auth-pass
266+
exec: # exec-based plugin
267+
command: gke-gcloud-auth-plugin
268+
args: ["--flag"]
269+
env: [{name: "FOO", value: "bar"}]
270+
apiVersion: client.authentication.k8s.io/v1
271+
provideClusterInfo: true
272+
installHint: "Install via: gcloud components install gke-gcloud-auth-plugin"
273+
auth-provider: # OIDC/OAuth2
274+
name: oidc
275+
config:
276+
idp-issuer-url: https://accounts.google.com
277+
client-id: my-client-id
278+
contexts:
279+
- name: my-context
280+
context:
281+
cluster: my-cluster
282+
user: my-user
283+
namespace: default
284+
```
285+
286+
**Proposed API**:
235287

236288
```typescript
237-
// Future API
289+
// Load from default location (~/.kube/config or $KUBECONFIG)
290+
const client = new KubernetesClient(KubernetesClient.fromKubeConfig());
291+
292+
// Load from specific file with context override
238293
const client = new KubernetesClient(
239-
KubernetesClient.fromKubeConfig({ context: 'my-cluster' })
294+
KubernetesClient.fromKubeConfig({
295+
kubeconfig: '/path/to/config',
296+
context: 'staging-cluster',
297+
})
240298
);
241299
```
242300

243-
**Challenge**: Requires a YAML parser. Options include adding `js-yaml` as an optional peer dependency, or implementing minimal YAML parsing for the kubeconfig subset.
301+
**client-go merge rules** (to implement):
302+
- `KUBECONFIG` can list multiple files separated by `:` (Unix) or `;` (Windows)
303+
- Map entries (clusters, users, contexts): **first file wins**
304+
- Scalar values (current-context): **last file wins**
305+
- Relative paths resolved against each file's directory
306+
- If no kubeconfig found, fall back to in-cluster config
307+
308+
**Implementation notes**:
309+
- Requires a YAML parser — options: `js-yaml` as optional peer dep, or minimal JSON-subset parser for kubeconfig
310+
- Should support both file paths and inline data (`-data` fields are base64-encoded PEM)
311+
- The `KUBECONFIG` env var handling should match client-go exactly
244312

245-
### TLS / CA Certificate Handling
313+
### Priority 2: TLS / CA Certificate Handling
246314

247-
Native support for custom CA certificates without relying on `NODE_EXTRA_CA_CERTS`. This would require creating a custom `fetch` agent with TLS options:
315+
**client-go reference**: `TLSClientConfig`, `transport.TLSConfigFor()`
316+
317+
client-go builds a `tls.Config` with:
318+
- `RootCAs` from `CAFile`/`CAData` for server verification
319+
- Minimum TLS 1.2
320+
- Optional `InsecureSkipVerify` for dev
321+
- SNI via `ServerName`
322+
323+
**Proposed API**:
248324

249325
```typescript
250-
// Future API
251326
const client = new KubernetesClient({
252327
restEndpoint: 'https://my-cluster:6443',
253328
token: myToken,
254329
tls: {
255330
caFile: '/path/to/ca.crt',
256-
// or caData: Buffer.from('...')
257-
insecureSkipVerify: false, // for dev only
331+
caData: '<base64-pem>', // inline CA (overrides caFile)
332+
insecureSkipVerify: false, // for dev only
333+
serverName: 'custom-sni.example.com',
258334
}
259335
});
260336
```
261337

262-
### Client Certificate Auth (mTLS)
338+
**Implementation notes**:
339+
- Node.js `fetch` (undici) supports custom `dispatcher` with TLS options as of Node 18.x
340+
- For the `ca` option, use `node:tls` to create a custom agent
341+
- `NODE_EXTRA_CA_CERTS` remains the simplest approach for in-cluster; native TLS config is for advanced use cases
342+
- Browser environments don't support custom CA certs — document this limitation
343+
344+
### Priority 3: Token File Watching
345+
346+
**client-go reference**: `BearerTokenFile` field, `BearerAuthWithRefreshRoundTripper`
347+
348+
In client-go, when `BearerTokenFile` is set, the token is re-read from disk on every request. This supports Kubernetes bound service account tokens which rotate automatically (default expiry: 1 hour).
349+
350+
**Proposed API**:
351+
352+
```typescript
353+
const client = new KubernetesClient({
354+
restEndpoint: 'https://...',
355+
tokenFile: '/var/run/secrets/kubernetes.io/serviceaccount/token',
356+
// Token is re-read from file before each request
357+
});
358+
```
359+
360+
**Implementation notes**:
361+
- Read token on each `request()` call (like client-go's round tripper)
362+
- Cache the token + file mtime to avoid unnecessary reads
363+
- Fall back to static `token` if `tokenFile` read fails after initial success
364+
- `tokenFile` takes precedence over `token` (matching client-go behavior)
365+
366+
### Priority 4: Client Certificate Auth (mTLS)
367+
368+
**client-go reference**: `TLSClientConfig.CertFile/CertData/KeyFile/KeyData`
369+
370+
Used when kubeconfig specifies `client-certificate` and `client-key`.
263371

264372
```typescript
265-
// Future API
266373
const client = new KubernetesClient({
267374
restEndpoint: 'https://my-cluster:6443',
268375
tls: {
269376
certFile: '/path/to/client.crt',
377+
certData: '<base64-pem>',
270378
keyFile: '/path/to/client.key',
379+
keyData: '<base64-pem>',
271380
caFile: '/path/to/ca.crt',
272381
}
273382
});
274383
```
275384

276-
### Token File Watching
385+
**Implementation notes**:
386+
- Requires `node:https` Agent with `cert`/`key`/`ca` options
387+
- Inline data (`-Data` fields) takes precedence over file paths (matching client-go)
388+
- Certificate rotation: client-go uses `GetClientCertificate` callback to reload certs, with a 5-minute refresh interval
389+
390+
### Priority 5: Exec-Based Credential Plugins
391+
392+
**client-go reference**: `ExecProvider`, `exec.Authenticator`
393+
394+
This is how GKE, EKS, and AKS authenticate — an external command produces credentials.
395+
396+
**Protocol**:
397+
1. Client executes the command with `KUBERNETES_EXEC_INFO` env var containing a JSON `ExecCredential` object
398+
2. Plugin outputs a JSON `ExecCredential` with `status.token` or `status.clientCertificateData`/`status.clientKeyData`
399+
3. Client caches credentials until `status.expirationTimestamp`
400+
401+
**ExecCredential format**:
402+
403+
```json
404+
{
405+
"apiVersion": "client.authentication.k8s.io/v1",
406+
"kind": "ExecCredential",
407+
"spec": {
408+
"interactive": false,
409+
"cluster": {
410+
"server": "https://...",
411+
"certificateAuthorityData": "...",
412+
"config": null
413+
}
414+
},
415+
"status": {
416+
"token": "the-bearer-token",
417+
"expirationTimestamp": "2024-01-01T00:00:00Z"
418+
}
419+
}
420+
```
277421

278-
Automatic re-reading of the SA token file on each request (or periodically), supporting Kubernetes bound service account token rotation:
422+
**Proposed API**:
279423

280424
```typescript
281-
// Future API
282425
const client = new KubernetesClient({
283426
restEndpoint: 'https://...',
284-
tokenFile: '/var/run/secrets/kubernetes.io/serviceaccount/token',
285-
// Token is re-read from file before each request
427+
exec: {
428+
command: 'gke-gcloud-auth-plugin',
429+
args: ['--flag'],
430+
env: [{ name: 'FOO', value: 'bar' }],
431+
apiVersion: 'client.authentication.k8s.io/v1',
432+
provideClusterInfo: true,
433+
installHint: 'Install via: gcloud components install gke-gcloud-auth-plugin',
434+
}
286435
});
287436
```
288437

289-
### Exec-Based Credential Plugins
438+
**Implementation notes**:
439+
- Use `node:child_process.execFile` (not `exec`, to prevent shell injection)
440+
- Cache credentials based on `expirationTimestamp`
441+
- Show `installHint` when command is not found
442+
- `provideClusterInfo: true` passes cluster server + CA data to the plugin via `KUBERNETES_EXEC_INFO`
443+
444+
### Priority 6: Watch / Streaming Connections
290445

291-
Support for external credential commands (used by GKE, EKS, AKS):
446+
**client-go reference**: `Watcher`, `Reflector`, `SharedInformer`
447+
448+
client-go's watch system:
449+
1. Makes HTTP GET with `?watch=true` query param
450+
2. API server streams line-delimited JSON `WatchEvent` objects
451+
3. Each event has `type` (ADDED/MODIFIED/DELETED/BOOKMARK/ERROR) and `object`
452+
4. On disconnect, reconnects using `resourceVersion` to resume
453+
5. `SharedInformer` provides a local cache with event handlers
454+
455+
**Proposed API**:
292456

293457
```typescript
294-
// Future API
295-
const client = new KubernetesClient({
296-
restEndpoint: 'https://...',
297-
exec: {
298-
command: 'gke-gcloud-auth-plugin',
299-
apiVersion: 'client.authentication.k8s.io/v1beta1',
300-
}
458+
// Low-level watch (returns async iterator)
459+
const watcher = await client.watchCoreV1NamespacedPod({
460+
path: { namespace: 'default' },
461+
query: { resourceVersion: '12345' },
301462
});
463+
464+
for await (const event of watcher) {
465+
console.log(event.type, event.object.metadata.name);
466+
// event.type: 'ADDED' | 'MODIFIED' | 'DELETED' | 'BOOKMARK' | 'ERROR'
467+
}
468+
469+
// High-level informer (future)
470+
const informer = client.inform('v1', 'pods', 'default');
471+
informer.on('add', (pod) => { ... });
472+
informer.on('update', (oldPod, newPod) => { ... });
473+
informer.on('delete', (pod) => { ... });
474+
await informer.start();
302475
```
303476

304-
### OIDC / Auth Provider Plugins
477+
**Implementation notes**:
478+
- Use `fetch` with streaming response body (`response.body` is a `ReadableStream`)
479+
- Parse line-delimited JSON using a `TransformStream` or manual line splitting
480+
- Track `resourceVersion` from each event for reconnection
481+
- Watch requests should NOT be rate-limited (matching client-go)
482+
- Implement reconnection with exponential backoff
483+
- `SharedInformer` pattern: single watch connection + local cache + event distribution
305484

306-
OpenID Connect token authentication for identity-based access.
485+
### Priority 7: Impersonation
307486

308-
### Impersonation
487+
**client-go reference**: `Impersonate` in `rest.Config`
309488

310489
```typescript
311-
// Future API
312490
const client = new KubernetesClient({
313491
restEndpoint: 'https://...',
314492
token: adminToken,
315493
impersonate: {
316494
user: 'jane@example.com',
317-
groups: ['developers'],
495+
uid: '12345',
496+
groups: ['developers', 'qa'],
497+
extra: { 'scopes': ['view', 'edit'] },
318498
}
319499
});
320500
```
501+
502+
Injects headers: `Impersonate-User`, `Impersonate-Uid`, `Impersonate-Group`, `Impersonate-Extra-<key>`.
503+
504+
### Priority 8: Rate Limiting & Retry
505+
506+
**client-go reference**: `QPS`/`Burst` fields, `BackoffManager`, `Retry-After` handling
507+
508+
client-go defaults: QPS=5, Burst=10. Uses token bucket rate limiter. Retries on 429 and 5xx with `Retry-After` header respect.
509+
510+
**Proposed API**:
511+
512+
```typescript
513+
const client = new KubernetesClient({
514+
restEndpoint: 'https://...',
515+
rateLimiter: {
516+
qps: 5,
517+
burst: 10,
518+
},
519+
retry: {
520+
maxRetries: 3,
521+
retryOn: [429, 500, 502, 503, 504],
522+
respectRetryAfter: true,
523+
}
524+
});
525+
```
526+
527+
### Priority 9: Connection Pooling & HTTP/2
528+
529+
**client-go reference**: `http.Transport` settings
530+
531+
client-go defaults:
532+
- `MaxIdleConnsPerHost`: 25
533+
- `IdleConnTimeout`: 90s (Go default)
534+
- HTTP/2 enabled by default via ALPN `["h2", "http/1.1"]`
535+
536+
In Node.js, `fetch` (undici) handles connection pooling internally. HTTP/2 support depends on the Node.js version and server configuration. These settings would be exposed via the custom dispatcher/agent.
537+
538+
### Priority 10: Patch Helpers
539+
540+
**client-go reference**: `types.JSONPatchType`, `types.MergePatchType`, `types.StrategicMergePatchType`, `types.ApplyPatchType`
541+
542+
Kubernetes supports four patch strategies:
543+
- **JSON Patch** (`application/json-patch+json`) — RFC 6902 operations array
544+
- **Merge Patch** (`application/merge-patch+json`) — partial JSON merge
545+
- **Strategic Merge Patch** (`application/strategic-merge-patch+json`) — K8s-aware list merging
546+
- **Server-Side Apply** (`application/apply-patch+yaml`) — field ownership management
547+
548+
Currently kubernetesjs uses `Content-Type: application/json` for PATCH. We should support setting the patch type:
549+
550+
```typescript
551+
await client.patchCoreV1NamespacedPod({
552+
path: { namespace: 'default', name: 'my-pod' },
553+
body: [{ op: 'replace', path: '/metadata/labels/app', value: 'new-value' }],
554+
}, { patchType: 'json' });
555+
556+
// Server-side apply
557+
await client.patchCoreV1NamespacedDeployment({
558+
path: { namespace: 'default', name: 'my-deploy' },
559+
body: deploymentManifest,
560+
query: { fieldManager: 'my-controller', force: true },
561+
}, { patchType: 'apply' });
562+
```
563+
564+
### Not Planned
565+
566+
- **Basic auth** (`username`/`password`) — deprecated in Kubernetes, removed from most distributions
567+
- **OIDC auth provider plugin** — deprecated in client-go in favor of exec-based plugins

0 commit comments

Comments
 (0)