@intentius/chant-k8s-client 0.52.2 → 0.53.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client.d.ts +21 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/client.test.ts +67 -0
- package/src/client.ts +71 -0
- package/src/index.ts +1 -1
package/dist/client.d.ts
CHANGED
|
@@ -199,9 +199,30 @@ export interface K8sClient {
|
|
|
199
199
|
* Cached for the client's lifetime, like the rest of discovery.
|
|
200
200
|
*/
|
|
201
201
|
preferredGroupVersions(signal?: AbortSignal): Promise<string[]>;
|
|
202
|
+
/**
|
|
203
|
+
* Who the API server says this client authenticates as (chant #1982) —
|
|
204
|
+
* `SelfSubjectReview`, the call behind `kubectl auth whoami`. `undefined`
|
|
205
|
+
* when the cluster serves no version of the review API, which is an answer
|
|
206
|
+
* rather than a failure: an older cluster simply cannot be asked.
|
|
207
|
+
*
|
|
208
|
+
* Non-mutating despite the POST: the review is evaluated against the
|
|
209
|
+
* request's own credentials and never persisted, which is what lets a
|
|
210
|
+
* read-only pre-flight command use it.
|
|
211
|
+
*/
|
|
212
|
+
selfSubjectReview(signal?: AbortSignal): Promise<SelfSubjectInfo | undefined>;
|
|
202
213
|
/** The API resource lists discovery has been asked for so far, for tests and diagnostics. */
|
|
203
214
|
discoveryCacheKeys(): string[];
|
|
204
215
|
}
|
|
216
|
+
/**
|
|
217
|
+
* The subject a `SelfSubjectReview` reports. A principal and its groups —
|
|
218
|
+
* never a token, and the API server never echoes one back.
|
|
219
|
+
*/
|
|
220
|
+
export interface SelfSubjectInfo {
|
|
221
|
+
/** `system:serviceaccount:<ns>:<name>`, an OIDC subject, a certificate CN. */
|
|
222
|
+
username: string;
|
|
223
|
+
uid?: string;
|
|
224
|
+
groups?: string[];
|
|
225
|
+
}
|
|
205
226
|
/**
|
|
206
227
|
* Build a client. Nothing is read from the network here — the kubeconfig is
|
|
207
228
|
* parsed, the context resolved, and the credential policy enforced, all before
|
package/dist/client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* it stays that way.
|
|
13
13
|
*/
|
|
14
14
|
export { createK8sClient, readAmbientContext, readKubeconfigView, loadClientNode, isK8sClientAvailable, apiVersionPath, splitApiVersion, selectorText, refText, } from "./client.js";
|
|
15
|
-
export type { K8sClient, ReadOptions, ApplyOptions, DeleteOptions, ListOptions, ReadLogOptions } from "./client.js";
|
|
15
|
+
export type { K8sClient, ReadOptions, ApplyOptions, DeleteOptions, ListOptions, ReadLogOptions, SelfSubjectInfo } from "./client.js";
|
|
16
16
|
export { K8sApiError, K8sTransportError, K8sClientUnavailableError, ExecCredentialNotAllowedError, FieldManagerError, KubeConfigError, UnknownResourceError, } from "./errors.js";
|
|
17
17
|
export type { K8sStatus } from "./errors.js";
|
|
18
18
|
export { CHANT_FIELD_MANAGER, FIELD_MANAGER_SEPARATOR, FIELD_MANAGER_MAX_LENGTH, fieldManagerFor, assertValidFieldManager, isChantFieldManager, chantStackOf, } from "./field-manager.js";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EACL,eAAe,EACf,kBAAkB,EAClB,kBAAkB,EAClB,cAAc,EACd,oBAAoB,EACpB,cAAc,EACd,eAAe,EACf,YAAY,EACZ,OAAO,GACR,MAAM,UAAU,CAAC;AAClB,YAAY,EAAE,SAAS,EAAE,WAAW,EAAE,YAAY,EAAE,aAAa,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EACL,eAAe,EACf,kBAAkB,EAClB,kBAAkB,EAClB,cAAc,EACd,oBAAoB,EACpB,cAAc,EACd,eAAe,EACf,YAAY,EACZ,OAAO,GACR,MAAM,UAAU,CAAC;AAClB,YAAY,EAAE,SAAS,EAAE,WAAW,EAAE,YAAY,EAAE,aAAa,EAAE,WAAW,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAElI,OAAO,EACL,WAAW,EACX,iBAAiB,EACjB,yBAAyB,EACzB,6BAA6B,EAC7B,iBAAiB,EACjB,eAAe,EACf,oBAAoB,GACrB,MAAM,UAAU,CAAC;AAClB,YAAY,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAG1C,OAAO,EACL,mBAAmB,EACnB,uBAAuB,EACvB,wBAAwB,EACxB,eAAe,EACf,uBAAuB,EACvB,mBAAmB,EACnB,YAAY,GACb,MAAM,iBAAiB,CAAC;AACzB,YAAY,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAE5D,OAAO,EACL,yBAAyB,EACzB,sBAAsB,EACtB,mBAAmB,EACnB,oBAAoB,EACpB,oBAAoB,GACrB,MAAM,YAAY,CAAC;AACpB,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAGhE,OAAO,EACL,eAAe,EACf,WAAW,EACX,UAAU,EACV,aAAa,EACb,gBAAgB,EAChB,WAAW,EACX,YAAY,EACZ,aAAa,GACd,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAE5E,OAAO,EACL,sBAAsB,EACtB,2BAA2B,EAC3B,gBAAgB,EAChB,YAAY,EACZ,eAAe,GAChB,MAAM,eAAe,CAAC;AACvB,YAAY,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAEhE,OAAO,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAEnE,YAAY,EACV,eAAe,EACf,gBAAgB,EAChB,cAAc,EACd,gBAAgB,EAChB,qBAAqB,EACrB,cAAc,EACd,SAAS,EACT,SAAS,EACT,kBAAkB,EAClB,YAAY,EACZ,gBAAgB,EAChB,mBAAmB,GACpB,MAAM,SAAS,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@intentius/chant-k8s-client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.53.0",
|
|
4
4
|
"description": "Typed Kubernetes API client for chant — the read/write path of the k8s lexicon, kept out of the build path",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://intentius.io/chant/lexicons/k8s/api-client/",
|
package/src/client.test.ts
CHANGED
|
@@ -637,3 +637,70 @@ describe("apply", () => {
|
|
|
637
637
|
expect(layer.requests).toHaveLength(0);
|
|
638
638
|
});
|
|
639
639
|
});
|
|
640
|
+
|
|
641
|
+
describe("selfSubjectReview — who the API server says this client is (#1982)", () => {
|
|
642
|
+
const REVIEW_V1 = "/apis/authentication.k8s.io/v1/selfsubjectreviews";
|
|
643
|
+
const REVIEW_V1BETA1 = "/apis/authentication.k8s.io/v1beta1/selfsubjectreviews";
|
|
644
|
+
|
|
645
|
+
function reviewBody(username: string, groups: string[] = []): Record<string, unknown> {
|
|
646
|
+
return {
|
|
647
|
+
apiVersion: "authentication.k8s.io/v1",
|
|
648
|
+
kind: "SelfSubjectReview",
|
|
649
|
+
status: { userInfo: { username, uid: "uid-1", groups } },
|
|
650
|
+
};
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
test("returns the subject the server authenticated, over a POST that creates nothing", async () => {
|
|
654
|
+
const layer = cluster({}, (req) =>
|
|
655
|
+
req.path === REVIEW_V1 ? { body: reviewBody("system:serviceaccount:chant:deployer", ["system:authenticated"]) } : undefined,
|
|
656
|
+
);
|
|
657
|
+
const c = await client(layer);
|
|
658
|
+
await expect(c.selfSubjectReview()).resolves.toEqual({
|
|
659
|
+
username: "system:serviceaccount:chant:deployer",
|
|
660
|
+
uid: "uid-1",
|
|
661
|
+
groups: ["system:authenticated"],
|
|
662
|
+
});
|
|
663
|
+
const sent = layer.requests.find((r) => r.path === REVIEW_V1)!;
|
|
664
|
+
expect(sent.method).toBe("POST");
|
|
665
|
+
expect(JSON.parse(sent.body as string)).toMatchObject({ kind: "SelfSubjectReview" });
|
|
666
|
+
// The review resource carries no name and no namespace: there is nothing
|
|
667
|
+
// for the server to persist, which is what makes it safe pre-flight.
|
|
668
|
+
expect(JSON.stringify(sent.body)).not.toContain("metadata");
|
|
669
|
+
});
|
|
670
|
+
|
|
671
|
+
test("falls back through the older API versions when v1 is not served", async () => {
|
|
672
|
+
const layer = cluster({}, (req) =>
|
|
673
|
+
req.path === REVIEW_V1BETA1 ? { body: reviewBody("kubernetes-admin") } : undefined,
|
|
674
|
+
);
|
|
675
|
+
const c = await client(layer);
|
|
676
|
+
await expect(c.selfSubjectReview()).resolves.toMatchObject({ username: "kubernetes-admin" });
|
|
677
|
+
expect(layer.paths()).toContain(REVIEW_V1);
|
|
678
|
+
expect(layer.paths()).toContain(REVIEW_V1BETA1);
|
|
679
|
+
});
|
|
680
|
+
|
|
681
|
+
test("a cluster serving no version at all answers undefined, not an error", async () => {
|
|
682
|
+
const c = await client(cluster());
|
|
683
|
+
await expect(c.selfSubjectReview()).resolves.toBeUndefined();
|
|
684
|
+
});
|
|
685
|
+
|
|
686
|
+
test("a refusal propagates rather than degrading to 'no such API'", async () => {
|
|
687
|
+
const layer = cluster({}, (req) =>
|
|
688
|
+
req.path === REVIEW_V1
|
|
689
|
+
? { status: 403, body: statusBody(403, "Forbidden", "selfsubjectreviews is forbidden") }
|
|
690
|
+
: undefined,
|
|
691
|
+
);
|
|
692
|
+
const c = await client(layer);
|
|
693
|
+
await expect(c.selfSubjectReview()).rejects.toBeInstanceOf(K8sApiError);
|
|
694
|
+
});
|
|
695
|
+
|
|
696
|
+
test("no credential is echoed back, and the answer carries only the subject", async () => {
|
|
697
|
+
const layer = cluster({}, (req) => (req.path === REVIEW_V1 ? { body: reviewBody("ci@acme.example") } : undefined));
|
|
698
|
+
const c = await createK8sClient({
|
|
699
|
+
kubeconfig: fakeKubeconfig({ token: "a-static-bearer-token-value" }),
|
|
700
|
+
requestLayer: layer,
|
|
701
|
+
});
|
|
702
|
+
const subject = await c.selfSubjectReview();
|
|
703
|
+
expect(JSON.stringify(subject)).not.toContain("a-static-bearer-token-value");
|
|
704
|
+
expect(c.provenance.credential).toBe("token");
|
|
705
|
+
});
|
|
706
|
+
});
|
package/src/client.ts
CHANGED
|
@@ -287,10 +287,39 @@ export interface K8sClient {
|
|
|
287
287
|
* Cached for the client's lifetime, like the rest of discovery.
|
|
288
288
|
*/
|
|
289
289
|
preferredGroupVersions(signal?: AbortSignal): Promise<string[]>;
|
|
290
|
+
/**
|
|
291
|
+
* Who the API server says this client authenticates as (chant #1982) —
|
|
292
|
+
* `SelfSubjectReview`, the call behind `kubectl auth whoami`. `undefined`
|
|
293
|
+
* when the cluster serves no version of the review API, which is an answer
|
|
294
|
+
* rather than a failure: an older cluster simply cannot be asked.
|
|
295
|
+
*
|
|
296
|
+
* Non-mutating despite the POST: the review is evaluated against the
|
|
297
|
+
* request's own credentials and never persisted, which is what lets a
|
|
298
|
+
* read-only pre-flight command use it.
|
|
299
|
+
*/
|
|
300
|
+
selfSubjectReview(signal?: AbortSignal): Promise<SelfSubjectInfo | undefined>;
|
|
290
301
|
/** The API resource lists discovery has been asked for so far, for tests and diagnostics. */
|
|
291
302
|
discoveryCacheKeys(): string[];
|
|
292
303
|
}
|
|
293
304
|
|
|
305
|
+
/**
|
|
306
|
+
* The subject a `SelfSubjectReview` reports. A principal and its groups —
|
|
307
|
+
* never a token, and the API server never echoes one back.
|
|
308
|
+
*/
|
|
309
|
+
export interface SelfSubjectInfo {
|
|
310
|
+
/** `system:serviceaccount:<ns>:<name>`, an OIDC subject, a certificate CN. */
|
|
311
|
+
username: string;
|
|
312
|
+
uid?: string;
|
|
313
|
+
groups?: string[];
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/** The versions of the review API to try, newest first. `v1` landed in 1.28. */
|
|
317
|
+
const SELF_SUBJECT_REVIEW_VERSIONS = ["v1", "v1beta1", "v1alpha1"] as const;
|
|
318
|
+
|
|
319
|
+
interface SelfSubjectReviewResponse {
|
|
320
|
+
status?: { userInfo?: { username?: string; uid?: string; groups?: string[] } };
|
|
321
|
+
}
|
|
322
|
+
|
|
294
323
|
interface ApiResourceListResponse {
|
|
295
324
|
groupVersion?: string;
|
|
296
325
|
resources?: Array<{
|
|
@@ -750,6 +779,47 @@ export async function createK8sClient(options: K8sClientOptions = {}): Promise<K
|
|
|
750
779
|
});
|
|
751
780
|
}
|
|
752
781
|
|
|
782
|
+
/**
|
|
783
|
+
* `SelfSubjectReview` — what the API server says this client authenticates
|
|
784
|
+
* as (chant #1982). The `authentication.k8s.io` API is versioned, and the
|
|
785
|
+
* review moved from alpha through beta to `v1` in 1.28; each version is
|
|
786
|
+
* tried in turn and the first the cluster serves answers. A cluster serving
|
|
787
|
+
* none is `undefined` — an answer, not a failure.
|
|
788
|
+
*
|
|
789
|
+
* The endpoint is a POST because that is the shape of a non-persisting
|
|
790
|
+
* review resource: the server evaluates the request's own credentials and
|
|
791
|
+
* returns the result. It creates nothing, stores nothing and admits nothing,
|
|
792
|
+
* which is what makes it safe on a read-only pre-flight path. `kubectl auth
|
|
793
|
+
* whoami` is the same call.
|
|
794
|
+
*/
|
|
795
|
+
async function selfSubjectReview(signal?: AbortSignal): Promise<SelfSubjectInfo | undefined> {
|
|
796
|
+
for (const version of SELF_SUBJECT_REVIEW_VERSIONS) {
|
|
797
|
+
const path = `/apis/authentication.k8s.io/${version}/selfsubjectreviews`;
|
|
798
|
+
let response: SelfSubjectReviewResponse;
|
|
799
|
+
try {
|
|
800
|
+
response = await sendJson<SelfSubjectReviewResponse>(path, "POST", {
|
|
801
|
+
body: JSON.stringify({ apiVersion: `authentication.k8s.io/${version}`, kind: "SelfSubjectReview" }),
|
|
802
|
+
signal,
|
|
803
|
+
target: `self subject review (${version})`,
|
|
804
|
+
});
|
|
805
|
+
} catch (err) {
|
|
806
|
+
// 404 is "this cluster does not serve that version" — try the next.
|
|
807
|
+
// Anything else (401, 403, a transport fault) is the caller's answer.
|
|
808
|
+
if (err instanceof K8sApiError && err.notFound) continue;
|
|
809
|
+
throw err;
|
|
810
|
+
}
|
|
811
|
+
const user = response.status?.userInfo;
|
|
812
|
+
if (user?.username) {
|
|
813
|
+
return {
|
|
814
|
+
username: user.username,
|
|
815
|
+
...(user.uid ? { uid: user.uid } : {}),
|
|
816
|
+
...(user.groups && user.groups.length > 0 ? { groups: user.groups } : {}),
|
|
817
|
+
};
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
return undefined;
|
|
821
|
+
}
|
|
822
|
+
|
|
753
823
|
async function resourcesOf(apiVersion: string, signal?: AbortSignal): Promise<ApiResourceInfo[]> {
|
|
754
824
|
const list = await apiResourceList(apiVersion, signal);
|
|
755
825
|
if (!list) return [];
|
|
@@ -772,6 +842,7 @@ export async function createK8sClient(options: K8sClientOptions = {}): Promise<K
|
|
|
772
842
|
concurrently: (items, fn) => mapConcurrent(items, fn, concurrency),
|
|
773
843
|
resources: resourcesOf,
|
|
774
844
|
preferredGroupVersions,
|
|
845
|
+
selfSubjectReview,
|
|
775
846
|
discoveryCacheKeys: () => [...discoveryCache.keys()].sort(),
|
|
776
847
|
};
|
|
777
848
|
}
|
package/src/index.ts
CHANGED
|
@@ -23,7 +23,7 @@ export {
|
|
|
23
23
|
selectorText,
|
|
24
24
|
refText,
|
|
25
25
|
} from "./client";
|
|
26
|
-
export type { K8sClient, ReadOptions, ApplyOptions, DeleteOptions, ListOptions, ReadLogOptions } from "./client";
|
|
26
|
+
export type { K8sClient, ReadOptions, ApplyOptions, DeleteOptions, ListOptions, ReadLogOptions, SelfSubjectInfo } from "./client";
|
|
27
27
|
|
|
28
28
|
export {
|
|
29
29
|
K8sApiError,
|