@microagi/alchemy-gcp 0.11.6 → 0.11.7
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/CHANGELOG.md +13 -0
- package/lib/Iam/Operations.d.ts +42 -0
- package/lib/Iam/Operations.d.ts.map +1 -0
- package/lib/Iam/Operations.js +48 -0
- package/lib/Iam/Operations.js.map +1 -0
- package/lib/Iam/WorkloadIdentityPool.d.ts +100 -0
- package/lib/Iam/WorkloadIdentityPool.d.ts.map +1 -0
- package/lib/Iam/WorkloadIdentityPool.js +175 -0
- package/lib/Iam/WorkloadIdentityPool.js.map +1 -0
- package/lib/Iam/WorkloadIdentityPoolProvider.d.ts +124 -0
- package/lib/Iam/WorkloadIdentityPoolProvider.d.ts.map +1 -0
- package/lib/Iam/WorkloadIdentityPoolProvider.js +222 -0
- package/lib/Iam/WorkloadIdentityPoolProvider.js.map +1 -0
- package/lib/Iam/index.d.ts +4 -0
- package/lib/Iam/index.d.ts.map +1 -1
- package/lib/Iam/index.js +2 -0
- package/lib/Iam/index.js.map +1 -1
- package/lib/Providers.d.ts.map +1 -1
- package/lib/Providers.js +5 -1
- package/lib/Providers.js.map +1 -1
- package/package.json +4 -4
- package/src/Iam/Operations.ts +81 -0
- package/src/Iam/WorkloadIdentityPool.ts +309 -0
- package/src/Iam/WorkloadIdentityPoolProvider.ts +425 -0
- package/src/Iam/index.ts +10 -0
- package/src/Providers.ts +12 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,19 @@ All notable changes to `@microagi/alchemy-gcp`. The format follows
|
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this package
|
|
5
5
|
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## 0.11.7 — 2026-08-02
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **`GCP.WorkloadIdentityPool`** and **`GCP.WorkloadIdentityPoolProvider`** —
|
|
12
|
+
keyless federation into GCP from an external OIDC issuer, so a workload
|
|
13
|
+
outside GCP (an EKS/AKS pod, a CI job) can impersonate a service account
|
|
14
|
+
with no service-account key anywhere in the system. The provider exposes an
|
|
15
|
+
`audience` attribute (`//iam.googleapis.com/{name}`) for use in a credential
|
|
16
|
+
configuration. Both resources undelete rather than fail when they find a
|
|
17
|
+
soft-deleted predecessor, since GCP holds a deleted pool's ID for ~30 days
|
|
18
|
+
and would otherwise block a re-deploy under the same name.
|
|
19
|
+
|
|
7
20
|
## 0.11.6 — 2026-07-28
|
|
8
21
|
|
|
9
22
|
### Changed
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { ConfigError } from "@distilled.cloud/gcp";
|
|
2
|
+
import type * as iam from "@distilled.cloud/gcp/unstable/iam-v1";
|
|
3
|
+
import type { ScopedPlanStatusSession } from "alchemy/Cli/Cli";
|
|
4
|
+
import * as Effect from "effect/Effect";
|
|
5
|
+
/**
|
|
6
|
+
* Resolved callable signature of an `iam-v1` operations getter.
|
|
7
|
+
*
|
|
8
|
+
* Workload Identity pools and providers each have their own operations
|
|
9
|
+
* collection (`…WorkloadIdentityPoolsOperations`,
|
|
10
|
+
* `…WorkloadIdentityPoolsProvidersOperations`) rather than one shared
|
|
11
|
+
* endpoint, so this is generic over the getter and each resource passes its
|
|
12
|
+
* own — the same factory shape `ArtifactRegistry/Operations.ts` uses.
|
|
13
|
+
*/
|
|
14
|
+
type GetOperations = Effect.Success<typeof iam.getProjectsLocationsWorkloadIdentityPoolsOperations>;
|
|
15
|
+
/**
|
|
16
|
+
* Build the IAM long-running-operation polling helper.
|
|
17
|
+
*
|
|
18
|
+
* `iam-v1` operations use the standard `google.longrunning.Operation` shape —
|
|
19
|
+
* doneness is `.done === true` and failure is a populated `.error` (NOT
|
|
20
|
+
* Container's `.status === "DONE"`, and NOT Sqladmin's tri-state `.status`).
|
|
21
|
+
* Verified against the `Operation` interface in
|
|
22
|
+
* `vendor/distilled/packages/gcp/src/unstable-services/iam-v1.ts`.
|
|
23
|
+
*
|
|
24
|
+
* Operation names returned by pool/provider create and delete are already
|
|
25
|
+
* fully qualified (`projects/{p}/locations/global/workloadIdentityPools/{id}/operations/{op}`),
|
|
26
|
+
* so callers pass them straight through — no qualify dance.
|
|
27
|
+
*
|
|
28
|
+
* Schedule: exponential 1s → 1.5× growth, capped per-poll at 10s via
|
|
29
|
+
* `Schedule.min`, then a hard count cap of 60 retries via `Schedule.max`
|
|
30
|
+
* → ~10 min wall ceiling. Pool and provider CRUD normally settles in
|
|
31
|
+
* seconds; the ceiling exists so a stuck operation surfaces as an error
|
|
32
|
+
* rather than hanging a deploy indefinitely.
|
|
33
|
+
*
|
|
34
|
+
* The helper is a factory — the caller resolves the operations getter once at
|
|
35
|
+
* provider construction and passes the resulting callable here, so we never
|
|
36
|
+
* re-resolve services on each await.
|
|
37
|
+
*/
|
|
38
|
+
export declare const makeAwaitOperation: (getOperations: GetOperations, label: string) => (operationName: string, session: ScopedPlanStatusSession) => Effect.Effect<iam.Operation, ConfigError | {
|
|
39
|
+
_tag: "OperationPending";
|
|
40
|
+
} | iam.GetProjectsLocationsWorkloadIdentityPoolsOperationsError, never>;
|
|
41
|
+
export {};
|
|
42
|
+
//# sourceMappingURL=Operations.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Operations.d.ts","sourceRoot":"","sources":["../../src/Iam/Operations.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACnD,OAAO,KAAK,KAAK,GAAG,MAAM,sCAAsC,CAAC;AACjE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAE/D,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AAGxC;;;;;;;;GAQG;AACH,KAAK,aAAa,GAAG,MAAM,CAAC,OAAO,CACjC,OAAO,GAAG,CAAC,mDAAmD,CAC/D,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,kBAAkB,kBACd,aAAa,SACrB,MAAM;UAUiB,kBAAkB;wEAyB9C,CAAC"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { ConfigError } from "@distilled.cloud/gcp";
|
|
2
|
+
import * as Duration from "effect/Duration";
|
|
3
|
+
import * as Effect from "effect/Effect";
|
|
4
|
+
import * as Schedule from "effect/Schedule";
|
|
5
|
+
/**
|
|
6
|
+
* Build the IAM long-running-operation polling helper.
|
|
7
|
+
*
|
|
8
|
+
* `iam-v1` operations use the standard `google.longrunning.Operation` shape —
|
|
9
|
+
* doneness is `.done === true` and failure is a populated `.error` (NOT
|
|
10
|
+
* Container's `.status === "DONE"`, and NOT Sqladmin's tri-state `.status`).
|
|
11
|
+
* Verified against the `Operation` interface in
|
|
12
|
+
* `vendor/distilled/packages/gcp/src/unstable-services/iam-v1.ts`.
|
|
13
|
+
*
|
|
14
|
+
* Operation names returned by pool/provider create and delete are already
|
|
15
|
+
* fully qualified (`projects/{p}/locations/global/workloadIdentityPools/{id}/operations/{op}`),
|
|
16
|
+
* so callers pass them straight through — no qualify dance.
|
|
17
|
+
*
|
|
18
|
+
* Schedule: exponential 1s → 1.5× growth, capped per-poll at 10s via
|
|
19
|
+
* `Schedule.min`, then a hard count cap of 60 retries via `Schedule.max`
|
|
20
|
+
* → ~10 min wall ceiling. Pool and provider CRUD normally settles in
|
|
21
|
+
* seconds; the ceiling exists so a stuck operation surfaces as an error
|
|
22
|
+
* rather than hanging a deploy indefinitely.
|
|
23
|
+
*
|
|
24
|
+
* The helper is a factory — the caller resolves the operations getter once at
|
|
25
|
+
* provider construction and passes the resulting callable here, so we never
|
|
26
|
+
* re-resolve services on each await.
|
|
27
|
+
*/
|
|
28
|
+
export const makeAwaitOperation = (getOperations, label) => Effect.fn(function* (operationName, session) {
|
|
29
|
+
const op = yield* getOperations({ name: operationName }).pipe(Effect.flatMap((current) => current.done === true
|
|
30
|
+
? Effect.succeed(current)
|
|
31
|
+
: Effect.fail({ _tag: "OperationPending" })), Effect.retry({
|
|
32
|
+
while: (e) => e?._tag === "OperationPending",
|
|
33
|
+
schedule: Schedule.max([
|
|
34
|
+
Schedule.min([
|
|
35
|
+
Schedule.exponential(Duration.seconds(1), 1.5),
|
|
36
|
+
Schedule.spaced(Duration.seconds(10)),
|
|
37
|
+
]),
|
|
38
|
+
Schedule.recurs(60),
|
|
39
|
+
]).pipe(Schedule.tap(() => session.note(`Waiting for ${label} operation ${operationName}…`))),
|
|
40
|
+
}));
|
|
41
|
+
if (op.error) {
|
|
42
|
+
return yield* new ConfigError({
|
|
43
|
+
message: `${label} operation ${operationName} failed: ${op.error.message ?? JSON.stringify(op.error)}`,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
return op;
|
|
47
|
+
});
|
|
48
|
+
//# sourceMappingURL=Operations.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Operations.js","sourceRoot":"","sources":["../../src/Iam/Operations.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAGnD,OAAO,KAAK,QAAQ,MAAM,iBAAiB,CAAC;AAC5C,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,KAAK,QAAQ,MAAM,iBAAiB,CAAC;AAe5C;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAChC,aAA4B,EAC5B,KAAa,EACb,EAAE,CACF,MAAM,CAAC,EAAE,CAAC,QAAQ,CAAC,EACjB,aAAqB,EACrB,OAAgC;IAEhC,MAAM,EAAE,GAAG,KAAK,CAAC,CAAC,aAAa,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,CAAC,CAAC,IAAI,CAC3D,MAAM,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CACzB,OAAO,CAAC,IAAI,KAAK,IAAI;QACnB,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC;QACzB,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,kBAA2B,EAAE,CAAC,CACvD,EACD,MAAM,CAAC,KAAK,CAAC;QACX,KAAK,EAAE,CAAC,CAAoB,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,KAAK,kBAAkB;QAC/D,QAAQ,EAAE,QAAQ,CAAC,GAAG,CAAC;YACrB,QAAQ,CAAC,GAAG,CAAC;gBACX,QAAQ,CAAC,WAAW,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC;gBAC9C,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;aACtC,CAAC;YACF,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;SACpB,CAAC,CAAC,IAAI,CACL,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,CAChB,OAAO,CAAC,IAAI,CAAC,eAAe,KAAK,cAAc,aAAa,GAAG,CAAC,CACjE,CACF;KACF,CAAC,CACH,CAAC;IACF,IAAI,EAAE,CAAC,KAAK,EAAE,CAAC;QACb,OAAO,KAAK,CAAC,CAAC,IAAI,WAAW,CAAC;YAC5B,OAAO,EAAE,GAAG,KAAK,cAAc,aAAa,YAC1C,EAAE,CAAC,KAAK,CAAC,OAAO,IAAI,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,KAAK,CAC7C,EAAE;SACH,CAAC,CAAC;IACL,CAAC;IACD,OAAO,EAAE,CAAC;AACZ,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { Resource } from "alchemy";
|
|
2
|
+
import * as Provider from "alchemy/Provider";
|
|
3
|
+
import type * as GCP from "../Providers.ts";
|
|
4
|
+
/**
|
|
5
|
+
* A Workload Identity **pool** — the trust boundary that lets identities from
|
|
6
|
+
* outside GCP (another cloud's OIDC issuer, a Kubernetes cluster, a CI system)
|
|
7
|
+
* impersonate a {@link import("./ServiceAccount.ts").ServiceAccount} without
|
|
8
|
+
* anyone ever creating a service-account key.
|
|
9
|
+
*
|
|
10
|
+
* A pool on its own trusts nothing; it is a namespace. The trust is declared
|
|
11
|
+
* by a {@link import("./WorkloadIdentityPoolProvider.ts").WorkloadIdentityPoolProvider}
|
|
12
|
+
* inside it, and the grant by an `iam.workloadIdentityUser` binding on the
|
|
13
|
+
* target GSA.
|
|
14
|
+
*
|
|
15
|
+
* ### Project number, not project ID
|
|
16
|
+
*
|
|
17
|
+
* The `principalSet://` member strings that reference this pool require the
|
|
18
|
+
* project **number**, not its ID:
|
|
19
|
+
*
|
|
20
|
+
* ```
|
|
21
|
+
* principalSet://iam.googleapis.com/projects/123456789/locations/global/workloadIdentityPools/POOL/*
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* A member string built with the project ID is accepted by `setIamPolicy`
|
|
25
|
+
* and then silently never matches, which is a genuinely unpleasant thing to
|
|
26
|
+
* debug. Prefer passing the project number as `project` so `name` comes back
|
|
27
|
+
* in the form the bindings need.
|
|
28
|
+
*
|
|
29
|
+
* ### Deletion is soft
|
|
30
|
+
*
|
|
31
|
+
* `delete` puts the pool in state `DELETED` with an `expireTime` roughly 30
|
|
32
|
+
* days out; it is purged only after that. **The pool ID cannot be reused
|
|
33
|
+
* until it is purged.** Because of that, `reconcile` treats an existing
|
|
34
|
+
* soft-deleted pool as recoverable and undeletes it rather than failing —
|
|
35
|
+
* otherwise a destroy followed by a re-deploy inside the same month would be
|
|
36
|
+
* unrecoverable without renaming the pool.
|
|
37
|
+
*
|
|
38
|
+
* All mutating operations are long-running; the provider polls them to
|
|
39
|
+
* completion via {@link makeAwaitOperation}.
|
|
40
|
+
*
|
|
41
|
+
* @section Creating a WorkloadIdentityPool
|
|
42
|
+
* @example Trust an EKS cluster's OIDC issuer
|
|
43
|
+
* ```typescript
|
|
44
|
+
* const pool = yield* GCP.WorkloadIdentityPool("EksPool", {
|
|
45
|
+
* project: "123456789", // project NUMBER
|
|
46
|
+
* poolId: "eks-us-east-1",
|
|
47
|
+
* displayName: "EKS us-east-1",
|
|
48
|
+
* description: "Federates research-eks-us-east-1 service accounts.",
|
|
49
|
+
* });
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
export type WorkloadIdentityPoolProps = {
|
|
53
|
+
/**
|
|
54
|
+
* Project that owns the pool. Accepts the project ID or number; prefer the
|
|
55
|
+
* **number**, because `principalSet://` members require it (see above).
|
|
56
|
+
*/
|
|
57
|
+
project: string;
|
|
58
|
+
/**
|
|
59
|
+
* Pool ID, 4–32 characters of `[a-z0-9-]`. Immutable — changing it
|
|
60
|
+
* replaces the pool. The `gcp-` prefix is reserved by Google.
|
|
61
|
+
*/
|
|
62
|
+
poolId: string;
|
|
63
|
+
/** Display name, max 32 characters. */
|
|
64
|
+
displayName?: string;
|
|
65
|
+
/** Description, max 256 characters. */
|
|
66
|
+
description?: string;
|
|
67
|
+
/**
|
|
68
|
+
* Disable the pool. A disabled pool exchanges no tokens; existing tokens
|
|
69
|
+
* stop granting access and start working again if it is re-enabled.
|
|
70
|
+
*/
|
|
71
|
+
disabled?: boolean;
|
|
72
|
+
};
|
|
73
|
+
export type WorkloadIdentityPoolAttributes = {
|
|
74
|
+
/** Full resource name, `projects/{p}/locations/global/workloadIdentityPools/{id}`. */
|
|
75
|
+
name: string;
|
|
76
|
+
project: string;
|
|
77
|
+
poolId: string;
|
|
78
|
+
displayName: string | undefined;
|
|
79
|
+
/** Description with the alchemy ownership marker stripped. */
|
|
80
|
+
description: string | undefined;
|
|
81
|
+
state: string | undefined;
|
|
82
|
+
disabled: boolean | undefined;
|
|
83
|
+
};
|
|
84
|
+
export interface WorkloadIdentityPool extends Resource<"GCP.WorkloadIdentityPool", WorkloadIdentityPoolProps, WorkloadIdentityPoolAttributes, never, GCP.Providers> {
|
|
85
|
+
}
|
|
86
|
+
export declare const WorkloadIdentityPool: import("alchemy").ResourceClass<WorkloadIdentityPool>;
|
|
87
|
+
/** `projects/{project}/locations/global` — the only supported location. */
|
|
88
|
+
export declare const poolParent: (project: string) => string;
|
|
89
|
+
export declare const poolResourceName: (project: string, poolId: string) => string;
|
|
90
|
+
/**
|
|
91
|
+
* Alchemy provider factory for {@link WorkloadIdentityPool}.
|
|
92
|
+
*
|
|
93
|
+
* Deliberately NOT named `WorkloadIdentityPoolProvider`, which the repo's
|
|
94
|
+
* `X` + `XProvider` convention would suggest: GCP already uses that exact
|
|
95
|
+
* term for a different, user-facing resource — the OIDC provider inside a
|
|
96
|
+
* pool ({@link import("./WorkloadIdentityPoolProvider.ts").WorkloadIdentityPoolProvider}).
|
|
97
|
+
* The resource keeps GCP's name; this internal factory takes the awkward one.
|
|
98
|
+
*/
|
|
99
|
+
export declare const WorkloadIdentityPoolResourceProvider: () => import("effect/Layer").Layer<Provider.Provider<WorkloadIdentityPool>, never, import("@distilled.cloud/gcp").Credentials | import("effect/unstable/http/HttpClient").HttpClient | import("alchemy").Stack | import("alchemy").Stage>;
|
|
100
|
+
//# sourceMappingURL=WorkloadIdentityPool.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WorkloadIdentityPool.d.ts","sourceRoot":"","sources":["../../src/Iam/WorkloadIdentityPool.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAGnC,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAO7C,OAAO,KAAK,KAAK,GAAG,MAAM,iBAAiB,CAAC;AAG5C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AACH,MAAM,MAAM,yBAAyB,GAAG;IACtC;;;OAGG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,MAAM,EAAE,MAAM,CAAC;IACf,uCAAuC;IACvC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,uCAAuC;IACvC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,8BAA8B,GAAG;IAC3C,sFAAsF;IACtF,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,8DAA8D;IAC9D,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,QAAQ,EAAE,OAAO,GAAG,SAAS,CAAC;CAC/B,CAAC;AAEF,MAAM,WAAW,oBACf,SAAQ,QAAQ,CACd,0BAA0B,EAC1B,yBAAyB,EACzB,8BAA8B,EAC9B,KAAK,EACL,GAAG,CAAC,SAAS,CACd;CAAG;AAEN,eAAO,MAAM,oBAAoB,uDAEhC,CAAC;AAcF,2EAA2E;AAC3E,eAAO,MAAM,UAAU,YAAa,MAAM,WACF,CAAC;AAEzC,eAAO,MAAM,gBAAgB,YAAa,MAAM,UAAU,MAAM,WACN,CAAC;AAE3D;;;;;;;;GAQG;AACH,eAAO,MAAM,oCAAoC,2OA0K9C,CAAC"}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { ConfigError } from "@distilled.cloud/gcp";
|
|
2
|
+
import * as iam from "@distilled.cloud/gcp/unstable/iam-v1";
|
|
3
|
+
import { Resource } from "alchemy";
|
|
4
|
+
import { Unowned } from "alchemy/AdoptPolicy";
|
|
5
|
+
import { isResolved } from "alchemy/Diff";
|
|
6
|
+
import * as Provider from "alchemy/Provider";
|
|
7
|
+
import * as Effect from "effect/Effect";
|
|
8
|
+
import { descriptionHasAlchemyMarker, gcpAlchemyDescription, stripAlchemyMarker, } from "../Tags.js";
|
|
9
|
+
import { makeAwaitOperation } from "./Operations.js";
|
|
10
|
+
export const WorkloadIdentityPool = Resource("GCP.WorkloadIdentityPool");
|
|
11
|
+
/**
|
|
12
|
+
* Treat absent and empty-string as the same value.
|
|
13
|
+
*
|
|
14
|
+
* The patch below uses a FIXED `updateMask` while the body omits unset
|
|
15
|
+
* optional fields — deliberately, since under a field mask an omitted field
|
|
16
|
+
* means "clear it". The hazard is the comparison: if GCP echoes a cleared
|
|
17
|
+
* field back as `""` rather than omitting it, a raw `!==` against `undefined`
|
|
18
|
+
* would report drift on every deploy and churn a patch LRO forever.
|
|
19
|
+
*/
|
|
20
|
+
const sameText = (a, b) => (a ?? "") === (b ?? "");
|
|
21
|
+
/** `projects/{project}/locations/global` — the only supported location. */
|
|
22
|
+
export const poolParent = (project) => `projects/${project}/locations/global`;
|
|
23
|
+
export const poolResourceName = (project, poolId) => `${poolParent(project)}/workloadIdentityPools/${poolId}`;
|
|
24
|
+
/**
|
|
25
|
+
* Alchemy provider factory for {@link WorkloadIdentityPool}.
|
|
26
|
+
*
|
|
27
|
+
* Deliberately NOT named `WorkloadIdentityPoolProvider`, which the repo's
|
|
28
|
+
* `X` + `XProvider` convention would suggest: GCP already uses that exact
|
|
29
|
+
* term for a different, user-facing resource — the OIDC provider inside a
|
|
30
|
+
* pool ({@link import("./WorkloadIdentityPoolProvider.ts").WorkloadIdentityPoolProvider}).
|
|
31
|
+
* The resource keeps GCP's name; this internal factory takes the awkward one.
|
|
32
|
+
*/
|
|
33
|
+
export const WorkloadIdentityPoolResourceProvider = () => Provider.effect(WorkloadIdentityPool, Effect.gen(function* () {
|
|
34
|
+
const getPool = yield* iam.getProjectsLocationsWorkloadIdentityPools;
|
|
35
|
+
const createPool = yield* iam.createProjectsLocationsWorkloadIdentityPools;
|
|
36
|
+
const patchPool = yield* iam.patchProjectsLocationsWorkloadIdentityPools;
|
|
37
|
+
const deletePool = yield* iam.deleteProjectsLocationsWorkloadIdentityPools;
|
|
38
|
+
const undeletePool = yield* iam.undeleteProjectsLocationsWorkloadIdentityPools;
|
|
39
|
+
const getOperations = yield* iam.getProjectsLocationsWorkloadIdentityPoolsOperations;
|
|
40
|
+
const awaitOperation = makeAwaitOperation(getOperations, "Workload Identity pool");
|
|
41
|
+
// Absent and invisible collapse to the same thing, matching the
|
|
42
|
+
// ServiceAccount observer: a 403 on a resource we are about to create
|
|
43
|
+
// is indistinguishable from a 404 without extra permissions we may not
|
|
44
|
+
// have.
|
|
45
|
+
const observePool = (project, poolId) => getPool({ name: poolResourceName(project, poolId) }).pipe(Effect.catchTag("NotFound", () => Effect.succeed(undefined)), Effect.catchTag("Forbidden", () => Effect.succeed(undefined)));
|
|
46
|
+
const toAttrs = (project, poolId, pool) => ({
|
|
47
|
+
name: pool.name ?? poolResourceName(project, poolId),
|
|
48
|
+
project,
|
|
49
|
+
poolId,
|
|
50
|
+
displayName: pool.displayName,
|
|
51
|
+
description: stripAlchemyMarker(pool.description),
|
|
52
|
+
state: pool.state,
|
|
53
|
+
disabled: pool.disabled,
|
|
54
|
+
});
|
|
55
|
+
return {
|
|
56
|
+
stables: ["name", "project", "poolId"],
|
|
57
|
+
diff: Effect.fn(function* ({ olds = {}, news, output }) {
|
|
58
|
+
if (!isResolved(news))
|
|
59
|
+
return undefined;
|
|
60
|
+
const oldProps = olds;
|
|
61
|
+
// Prefer live attributes over persisted props, and fall back to the
|
|
62
|
+
// DESIRED value when neither is known — mirroring Project.ts.
|
|
63
|
+
//
|
|
64
|
+
// Defaulting to `undefined` instead would make an adoption (output
|
|
65
|
+
// present, olds absent) compare `undefined !== news.project` and
|
|
66
|
+
// return `replace`, deleting and recreating a live pool that was
|
|
67
|
+
// already correct. Both fields are part of the resource name, so a
|
|
68
|
+
// genuine change here really is a replacement; the point is only to
|
|
69
|
+
// avoid inventing one.
|
|
70
|
+
const currentProject = output?.project || oldProps.project || news.project;
|
|
71
|
+
const currentPoolId = output?.poolId || oldProps.poolId || news.poolId;
|
|
72
|
+
if (currentProject !== news.project || currentPoolId !== news.poolId) {
|
|
73
|
+
return { action: "replace" };
|
|
74
|
+
}
|
|
75
|
+
}),
|
|
76
|
+
read: Effect.fn(function* ({ id, olds, output }) {
|
|
77
|
+
const project = output?.project || olds?.project;
|
|
78
|
+
const poolId = output?.poolId || olds?.poolId;
|
|
79
|
+
if (!project || !poolId)
|
|
80
|
+
return undefined;
|
|
81
|
+
const pool = yield* observePool(project, poolId);
|
|
82
|
+
if (!pool)
|
|
83
|
+
return undefined;
|
|
84
|
+
const attrs = toAttrs(project, poolId, pool);
|
|
85
|
+
return (yield* descriptionHasAlchemyMarker(id, pool.description))
|
|
86
|
+
? attrs
|
|
87
|
+
: Unowned(attrs);
|
|
88
|
+
}),
|
|
89
|
+
reconcile: Effect.fn(function* ({ id, news, session }) {
|
|
90
|
+
const { project, poolId } = news;
|
|
91
|
+
const description = yield* gcpAlchemyDescription(id, news.description);
|
|
92
|
+
// Body is TOTAL over the patch's updateMask below: every masked
|
|
93
|
+
// field is always present, with an explicit empty/default value
|
|
94
|
+
// when the prop is unset. Behaviourally identical to omitting them
|
|
95
|
+
// (a masked-but-absent field is cleared), but it states the intent
|
|
96
|
+
// in code rather than relying on that field-mask subtlety, so
|
|
97
|
+
// "clear displayName" cannot be misread as "leave it alone".
|
|
98
|
+
const body = {
|
|
99
|
+
displayName: news.displayName ?? "",
|
|
100
|
+
description,
|
|
101
|
+
disabled: news.disabled ?? false,
|
|
102
|
+
};
|
|
103
|
+
let pool = yield* observePool(project, poolId);
|
|
104
|
+
// A soft-deleted pool holds its ID for ~30 days. Undelete rather
|
|
105
|
+
// than fail: otherwise destroy-then-redeploy is stuck until the
|
|
106
|
+
// purge, with no way out but renaming the pool.
|
|
107
|
+
if (pool?.state === "DELETED") {
|
|
108
|
+
yield* session.note(`Undeleting soft-deleted Workload Identity pool ${poolId}…`);
|
|
109
|
+
const op = yield* undeletePool({
|
|
110
|
+
name: poolResourceName(project, poolId),
|
|
111
|
+
body: {},
|
|
112
|
+
});
|
|
113
|
+
if (op.name)
|
|
114
|
+
yield* awaitOperation(op.name, session);
|
|
115
|
+
pool = yield* observePool(project, poolId);
|
|
116
|
+
}
|
|
117
|
+
if (!pool) {
|
|
118
|
+
const op = yield* createPool({
|
|
119
|
+
parent: poolParent(project),
|
|
120
|
+
workloadIdentityPoolId: poolId,
|
|
121
|
+
body,
|
|
122
|
+
});
|
|
123
|
+
if (op.name)
|
|
124
|
+
yield* awaitOperation(op.name, session);
|
|
125
|
+
pool = yield* observePool(project, poolId);
|
|
126
|
+
if (!pool) {
|
|
127
|
+
return yield* new ConfigError({
|
|
128
|
+
message: `Workload Identity pool ${poolId} in project ${project} was not readable after create.`,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
// Only the mutable fields, and only when they actually differ —
|
|
134
|
+
// an unconditional patch would churn an LRO on every deploy.
|
|
135
|
+
const needsPatch = !sameText(pool.displayName, news.displayName) ||
|
|
136
|
+
!sameText(pool.description, description) ||
|
|
137
|
+
(pool.disabled ?? false) !== (news.disabled ?? false);
|
|
138
|
+
if (needsPatch) {
|
|
139
|
+
const op = yield* patchPool({
|
|
140
|
+
name: poolResourceName(project, poolId),
|
|
141
|
+
updateMask: "displayName,description,disabled",
|
|
142
|
+
body,
|
|
143
|
+
});
|
|
144
|
+
if (op.name)
|
|
145
|
+
yield* awaitOperation(op.name, session);
|
|
146
|
+
pool = (yield* observePool(project, poolId)) ?? pool;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
yield* session.note(poolResourceName(project, poolId));
|
|
150
|
+
return toAttrs(project, poolId, pool);
|
|
151
|
+
}),
|
|
152
|
+
delete: Effect.fn(function* ({ olds, output, session }) {
|
|
153
|
+
// Fall back to props when attributes are incomplete. A create whose
|
|
154
|
+
// LRO succeeded but whose follow-up read failed leaves state with
|
|
155
|
+
// props and no usable attributes; a no-op destroy would then LEAK
|
|
156
|
+
// the pool — and because deletion is soft, the leaked pool holds
|
|
157
|
+
// its ID for ~30 days, blocking a re-deploy under the same name.
|
|
158
|
+
// `||` not `??`: an empty string is never a valid identity here,
|
|
159
|
+
// and a persisted `""` must fall through to the next source rather
|
|
160
|
+
// than be taken as real. With `??` a corrupt/partial state entry
|
|
161
|
+
// would short-circuit the fallback and turn destroy into a silent
|
|
162
|
+
// no-op (or make diff replace a live resource).
|
|
163
|
+
const project = output?.project || olds?.project;
|
|
164
|
+
const poolId = output?.poolId || olds?.poolId;
|
|
165
|
+
if (!project || !poolId)
|
|
166
|
+
return;
|
|
167
|
+
const op = yield* deletePool({
|
|
168
|
+
name: poolResourceName(project, poolId),
|
|
169
|
+
}).pipe(Effect.catchTag("NotFound", () => Effect.succeed({ name: undefined })));
|
|
170
|
+
if (op.name)
|
|
171
|
+
yield* awaitOperation(op.name, session);
|
|
172
|
+
}),
|
|
173
|
+
};
|
|
174
|
+
}));
|
|
175
|
+
//# sourceMappingURL=WorkloadIdentityPool.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WorkloadIdentityPool.js","sourceRoot":"","sources":["../../src/Iam/WorkloadIdentityPool.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { Resource } from "alchemy";
|
|
2
|
+
import * as Provider from "alchemy/Provider";
|
|
3
|
+
import type * as GCP from "../Providers.ts";
|
|
4
|
+
/**
|
|
5
|
+
* An OIDC **provider** inside a
|
|
6
|
+
* {@link import("./WorkloadIdentityPool.ts").WorkloadIdentityPool} — the
|
|
7
|
+
* thing that actually declares "I trust tokens issued by this URL".
|
|
8
|
+
*
|
|
9
|
+
* The canonical use is keyless federation from another cloud: point
|
|
10
|
+
* `oidc.issuerUri` at an EKS/AKS/GKE cluster's OIDC issuer, and pods holding
|
|
11
|
+
* a projected ServiceAccount token can impersonate a GSA with no key material
|
|
12
|
+
* anywhere in the system.
|
|
13
|
+
*
|
|
14
|
+
* ### `attributeMapping` is required and `google.subject` is mandatory
|
|
15
|
+
*
|
|
16
|
+
* The mapping turns claims on the incoming token into Google attributes. At
|
|
17
|
+
* minimum `google.subject` must be mapped; it is what `principal://` members
|
|
18
|
+
* match on. For a Kubernetes issuer the subject claim is
|
|
19
|
+
* `system:serviceaccount:{namespace}:{name}`, so:
|
|
20
|
+
*
|
|
21
|
+
* ```typescript
|
|
22
|
+
* attributeMapping: { "google.subject": "assertion.sub" }
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* ### Set `allowedAudiences`, or the default is surprising
|
|
26
|
+
*
|
|
27
|
+
* When `oidc.allowedAudiences` is empty, GCP accepts only the *default*
|
|
28
|
+
* audience — the full provider resource name prefixed with
|
|
29
|
+
* `https://iam.googleapis.com/`. A Kubernetes projected token minted for
|
|
30
|
+
* audience `sts.googleapis.com` (or anything else) is then rejected with a
|
|
31
|
+
* generic audience error. Set this explicitly to whatever audience the
|
|
32
|
+
* token is actually minted for.
|
|
33
|
+
*
|
|
34
|
+
* ### Constrain trust with `attributeCondition`
|
|
35
|
+
*
|
|
36
|
+
* An issuer-only trust accepts **every** identity that issuer can mint — on
|
|
37
|
+
* a Kubernetes cluster, that is every ServiceAccount in every namespace. Pair
|
|
38
|
+
* the provider with an `attributeCondition` (and a narrow `principal://`
|
|
39
|
+
* member on the GSA binding) so a token from an unrelated namespace cannot
|
|
40
|
+
* impersonate anything.
|
|
41
|
+
*
|
|
42
|
+
* @section Creating a WorkloadIdentityPoolProvider
|
|
43
|
+
* @example Trust one Kubernetes ServiceAccount on an EKS cluster
|
|
44
|
+
* ```typescript
|
|
45
|
+
* const provider = yield* GCP.WorkloadIdentityPoolProvider("EksOidc", {
|
|
46
|
+
* project: "123456789",
|
|
47
|
+
* poolId: pool.poolId,
|
|
48
|
+
* providerId: "eks-oidc",
|
|
49
|
+
* oidc: {
|
|
50
|
+
* issuerUri: "https://oidc.eks.us-east-1.amazonaws.com/id/ABC123",
|
|
51
|
+
* allowedAudiences: ["sts.googleapis.com"],
|
|
52
|
+
* },
|
|
53
|
+
* attributeMapping: { "google.subject": "assertion.sub" },
|
|
54
|
+
* attributeCondition:
|
|
55
|
+
* "assertion.sub == 'system:serviceaccount:admin:eks-auth-reconciler'",
|
|
56
|
+
* });
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
export type WorkloadIdentityPoolProviderProps = {
|
|
60
|
+
/** Project that owns the pool. Prefer the project **number**. */
|
|
61
|
+
project: string;
|
|
62
|
+
/** ID of the enclosing pool. */
|
|
63
|
+
poolId: string;
|
|
64
|
+
/**
|
|
65
|
+
* Provider ID, 4–32 characters of `[a-z0-9-]`. Immutable — changing it
|
|
66
|
+
* replaces the provider. The `gcp-` prefix is reserved by Google.
|
|
67
|
+
*/
|
|
68
|
+
providerId: string;
|
|
69
|
+
/** The OIDC issuer this provider trusts. */
|
|
70
|
+
oidc: {
|
|
71
|
+
/** Issuer URL. Must be HTTPS and serve an OIDC discovery document. */
|
|
72
|
+
issuerUri: string;
|
|
73
|
+
/**
|
|
74
|
+
* Acceptable `aud` values. Leave unset only if the token is minted for
|
|
75
|
+
* the provider's default audience — see the note above.
|
|
76
|
+
*/
|
|
77
|
+
allowedAudiences?: string[];
|
|
78
|
+
/**
|
|
79
|
+
* Inline JWKS, for issuers whose keys are not publicly reachable. When
|
|
80
|
+
* unset, GCP fetches `jwks_uri` from the issuer's discovery document,
|
|
81
|
+
* which requires the issuer to be reachable from Google's network.
|
|
82
|
+
*/
|
|
83
|
+
jwksJson?: string;
|
|
84
|
+
};
|
|
85
|
+
/**
|
|
86
|
+
* Claim-to-attribute mapping. Must include `google.subject`.
|
|
87
|
+
*/
|
|
88
|
+
attributeMapping: Record<string, string>;
|
|
89
|
+
/** CEL expression further restricting which credentials are accepted. */
|
|
90
|
+
attributeCondition?: string;
|
|
91
|
+
/** Display name, max 32 characters. */
|
|
92
|
+
displayName?: string;
|
|
93
|
+
/** Description, max 256 characters. */
|
|
94
|
+
description?: string;
|
|
95
|
+
/** Disable the provider without deleting it. */
|
|
96
|
+
disabled?: boolean;
|
|
97
|
+
};
|
|
98
|
+
export type WorkloadIdentityPoolProviderAttributes = {
|
|
99
|
+
/** Full resource name, `…/workloadIdentityPools/{pool}/providers/{id}`. */
|
|
100
|
+
name: string;
|
|
101
|
+
project: string;
|
|
102
|
+
poolId: string;
|
|
103
|
+
providerId: string;
|
|
104
|
+
issuerUri: string | undefined;
|
|
105
|
+
allowedAudiences: readonly string[] | undefined;
|
|
106
|
+
attributeMapping: Record<string, string> | undefined;
|
|
107
|
+
attributeCondition: string | undefined;
|
|
108
|
+
displayName: string | undefined;
|
|
109
|
+
description: string | undefined;
|
|
110
|
+
state: string | undefined;
|
|
111
|
+
disabled: boolean | undefined;
|
|
112
|
+
/**
|
|
113
|
+
* The audience string to put in a credential configuration, i.e.
|
|
114
|
+
* `//iam.googleapis.com/{name}`. Provided because assembling it by hand is
|
|
115
|
+
* easy to get subtly wrong (the leading `//`, and no scheme).
|
|
116
|
+
*/
|
|
117
|
+
audience: string;
|
|
118
|
+
};
|
|
119
|
+
export interface WorkloadIdentityPoolProvider extends Resource<"GCP.WorkloadIdentityPoolProvider", WorkloadIdentityPoolProviderProps, WorkloadIdentityPoolProviderAttributes, never, GCP.Providers> {
|
|
120
|
+
}
|
|
121
|
+
export declare const WorkloadIdentityPoolProvider: import("alchemy").ResourceClass<WorkloadIdentityPoolProvider>;
|
|
122
|
+
export declare const providerResourceName: (project: string, poolId: string, providerId: string) => string;
|
|
123
|
+
export declare const WorkloadIdentityPoolProviderProvider: () => import("effect/Layer").Layer<Provider.Provider<WorkloadIdentityPoolProvider>, never, import("@distilled.cloud/gcp").Credentials | import("effect/unstable/http/HttpClient").HttpClient | import("alchemy").Stack | import("alchemy").Stage>;
|
|
124
|
+
//# sourceMappingURL=WorkloadIdentityPoolProvider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WorkloadIdentityPoolProvider.d.ts","sourceRoot":"","sources":["../../src/Iam/WorkloadIdentityPoolProvider.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAGnC,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAO7C,OAAO,KAAK,KAAK,GAAG,MAAM,iBAAiB,CAAC;AAI5C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsDG;AACH,MAAM,MAAM,iCAAiC,GAAG;IAC9C,iEAAiE;IACjE,OAAO,EAAE,MAAM,CAAC;IAChB,gCAAgC;IAChC,MAAM,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB,4CAA4C;IAC5C,IAAI,EAAE;QACJ,sEAAsE;QACtE,SAAS,EAAE,MAAM,CAAC;QAClB;;;WAGG;QACH,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;QAC5B;;;;WAIG;QACH,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,CAAC;IACF;;OAEG;IACH,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzC,yEAAyE;IACzE,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,uCAAuC;IACvC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,uCAAuC;IACvC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gDAAgD;IAChD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,sCAAsC,GAAG;IACnD,2EAA2E;IAC3E,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IAG9B,gBAAgB,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;IAChD,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAAC;IACrD,kBAAkB,EAAE,MAAM,GAAG,SAAS,CAAC;IACvC,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,QAAQ,EAAE,OAAO,GAAG,SAAS,CAAC;IAC9B;;;;OAIG;IACH,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,MAAM,WAAW,4BACf,SAAQ,QAAQ,CACd,kCAAkC,EAClC,iCAAiC,EACjC,sCAAsC,EACtC,KAAK,EACL,GAAG,CAAC,SAAS,CACd;CAAG;AAEN,eAAO,MAAM,4BAA4B,+DACmC,CAAC;AAE7E,eAAO,MAAM,oBAAoB,YACtB,MAAM,UACP,MAAM,cACF,MAAM,WAC+C,CAAC;AAgDpE,eAAO,MAAM,oCAAoC,mPAiO9C,CAAC"}
|