| 1 | /** |
| 2 | * Internal composite: a Kubernetes service account (bound onto the cluster) |
| 3 | * wired to EKS Pod Identity through an IAM role + `PodIdentityAssociation`. |
| 4 | * Un-exported — the workload platforms (`Kubernetes.Deployment`, |
| 5 | * `Kubernetes.Job`) provision the same triple through the aws-eks |
| 6 | * adapter; this helper remains for composition-style stacks that assemble |
| 7 | * the pieces from resources. |
| 8 | */ |
| 9 | import * as Effect from "effect/Effect"; |
| 10 | import type { Input } from "../../../Input.ts"; |
| 11 | import * as Namespace from "../../../Namespace.ts"; |
| 12 | import type { PolicyDocument } from "../../IAM/Policy.ts"; |
| 13 | import { |
| 14 | Role, |
| 15 | type RoleArn, |
| 16 | type Role as RoleResource, |
| 17 | } from "../../IAM/Role.ts"; |
| 18 | import type { Cluster } from "../Cluster.ts"; |
| 19 | import { |
| 20 | PodIdentityAssociation, |
| 21 | type PodIdentityAssociation as PodIdentityAssociationResource, |
| 22 | } from "../PodIdentityAssociation.ts"; |
| 23 | import { |
| 24 | clusterServiceAccount, |
| 25 | namespaceNameOf, |
| 26 | type ClusterObjectRef, |
| 27 | } from "./ClusterObject.ts"; |
| 28 | |
| 29 | export interface PodIdentityServiceAccountProps { |
| 30 | /** Target EKS cluster. */ |
| 31 | cluster: Cluster; |
| 32 | /** Namespace name or namespace helper result. */ |
| 33 | namespace: string | { name: string } | ClusterObjectRef; |
| 34 | /** Optional explicit service account name. Defaults to the logical id. */ |
| 35 | serviceAccountName?: string; |
| 36 | /** Existing IAM role ARN to use for pod identity. */ |
| 37 | roleArn?: string; |
| 38 | /** Optional role name when Alchemy creates the IAM role. */ |
| 39 | roleName?: string; |
| 40 | /** Managed policy ARNs to attach when creating the IAM role. */ |
| 41 | managedPolicyArns?: string[]; |
| 42 | /** Inline policies to attach when creating the IAM role. */ |
| 43 | inlinePolicies?: Record<string, PolicyDocument>; |
| 44 | /** Optional role description when Alchemy creates the IAM role. */ |
| 45 | description?: string; |
| 46 | /** Disable session tags for the pod identity association. */ |
| 47 | disableSessionTags?: boolean; |
| 48 | /** Optional target role ARN for chained role assumption. */ |
| 49 | targetRoleArn?: string; |
| 50 | /** Optional inline session policy JSON. */ |
| 51 | policy?: string; |
| 52 | /** Labels applied to the Kubernetes service account. */ |
| 53 | labels?: Record<string, string>; |
| 54 | /** Annotations applied to the Kubernetes service account. */ |
| 55 | annotations?: Record<string, string>; |
| 56 | /** Tags applied to AWS resources. */ |
| 57 | tags?: Record<string, string>; |
| 58 | } |
| 59 | |
| 60 | export interface PodIdentityServiceAccountResources { |
| 61 | /** Reference to the created Kubernetes service account. */ |
| 62 | serviceAccount: ClusterObjectRef; |
| 63 | /** The Pod Identity association binding the role to the service account. */ |
| 64 | podIdentityAssociation: PodIdentityAssociationResource; |
| 65 | /** The generated IAM role, or `undefined` when `roleArn` was supplied. */ |
| 66 | role: RoleResource | undefined; |
| 67 | /** The ARN of the IAM role pods assume (generated or supplied). */ |
| 68 | roleArn: Input<string> | RoleArn; |
| 69 | } |
| 70 | |
| 71 | export const PodIdentityServiceAccount = ( |
| 72 | id: string, |
| 73 | props: PodIdentityServiceAccountProps, |
| 74 | ) => |
| 75 | Namespace.push( |
| 76 | id, |
| 77 | Effect.gen(function* () { |
| 78 | const serviceAccountName = props.serviceAccountName ?? id; |
| 79 | |
| 80 | const role = props.roleArn |
| 81 | ? undefined |
| 82 | : yield* Role("Role", { |
| 83 | roleName: props.roleName, |
| 84 | assumeRolePolicyDocument: { |
| 85 | Version: "2012-10-17", |
| 86 | Statement: [ |
| 87 | { |
| 88 | Effect: "Allow", |
| 89 | Principal: { |
| 90 | Service: "pods.eks.amazonaws.com", |
| 91 | }, |
| 92 | Action: ["sts:AssumeRole", "sts:TagSession"], |
| 93 | }, |
| 94 | ], |
| 95 | }, |
| 96 | description: |
| 97 | props.description ?? |
| 98 | `Pod identity role for service account ${serviceAccountName}.`, |
| 99 | managedPolicyArns: props.managedPolicyArns, |
| 100 | inlinePolicies: props.inlinePolicies, |
| 101 | tags: props.tags, |
| 102 | }); |
| 103 | |
| 104 | const serviceAccount = yield* clusterServiceAccount("ServiceAccount", { |
| 105 | cluster: props.cluster, |
| 106 | namespace: props.namespace, |
| 107 | name: serviceAccountName, |
| 108 | labels: props.labels, |
| 109 | annotations: props.annotations, |
| 110 | }); |
| 111 | |
| 112 | const podIdentityAssociation = yield* PodIdentityAssociation( |
| 113 | "PodIdentityAssociation", |
| 114 | { |
| 115 | clusterName: props.cluster.clusterName, |
| 116 | namespace: namespaceNameOf(props.namespace), |
| 117 | serviceAccount: serviceAccount.name, |
| 118 | roleArn: props.roleArn ?? role!.roleArn, |
| 119 | disableSessionTags: props.disableSessionTags, |
| 120 | targetRoleArn: props.targetRoleArn, |
| 121 | policy: props.policy, |
| 122 | tags: props.tags, |
| 123 | }, |
| 124 | ); |
| 125 | |
| 126 | return { |
| 127 | serviceAccount, |
| 128 | podIdentityAssociation, |
| 129 | role, |
| 130 | roleArn: props.roleArn ?? role!.roleArn, |
| 131 | } satisfies PodIdentityServiceAccountResources; |
| 132 | }), |
| 133 | ); |