|
| 1 | +// Package aws provides utilities for creating and managing AWS IAM resources |
| 2 | +// for Kubernetes workloads running in EKS. It handles the creation of IAM roles, |
| 3 | +// policies, and policy attachments that enable pod identity and KMS access. |
| 4 | +// |
| 5 | +// The package is designed to work with Pulumi and provides a high-level interface |
| 6 | +// for managing AWS IAM resources, particularly for services that need to interact |
| 7 | +// with AWS KMS for cryptographic operations. |
| 8 | +package aws |
| 9 | + |
| 10 | +import ( |
| 11 | + "encoding/json" |
| 12 | + "fmt" |
| 13 | + |
| 14 | + "github.com/pulumi/pulumi-aws/sdk/v6/go/aws/iam" |
| 15 | + "github.com/pulumi/pulumi/sdk/v3/go/pulumi" |
| 16 | +) |
| 17 | + |
| 18 | +// IAMResources contains AWS IAM resources created for a component. |
| 19 | +// These resources work together to provide the necessary permissions |
| 20 | +// for a Kubernetes workload to interact with AWS services. |
| 21 | +type IAMResources struct { |
| 22 | + // Role is the IAM role that can be assumed by the Kubernetes workload |
| 23 | + Role *iam.Role |
| 24 | + // Policy defines the permissions granted to the role |
| 25 | + Policy *iam.Policy |
| 26 | + // PolicyAttachment connects the policy to the role |
| 27 | + PolicyAttachment *iam.RolePolicyAttachment |
| 28 | +} |
| 29 | + |
| 30 | +// CreateIAMResources creates IAM resources (role, policy, and policy attachment) for a component. |
| 31 | +// It sets up the necessary permissions for a Kubernetes workload to interact with AWS KMS. |
| 32 | +// |
| 33 | +// Parameters: |
| 34 | +// - ctx: The Pulumi context |
| 35 | +// - name: Base name for the IAM resources |
| 36 | +// - serviceName: Name of the service that will use these IAM resources |
| 37 | +// - keyArn: ARN of the KMS key that the service needs to access |
| 38 | +// - parent: Parent Pulumi resource for dependency tracking |
| 39 | +// |
| 40 | +// Returns: |
| 41 | +// - *IAMResources: The created IAM resources |
| 42 | +// - error: Any error that occurred during creation |
| 43 | +// |
| 44 | +// Example: |
| 45 | +// |
| 46 | +// resources, err := CreateIAMResources(ctx, "my-service", "my-service", keyArn, parent) |
| 47 | +// if err != nil { |
| 48 | +// return nil, fmt.Errorf("failed to create IAM resources: %w", err) |
| 49 | +// } |
| 50 | +func CreateIAMResources( |
| 51 | + ctx *pulumi.Context, |
| 52 | + name string, |
| 53 | + serviceName string, |
| 54 | + keyArn pulumi.StringInput, |
| 55 | + parent pulumi.Resource, |
| 56 | +) (*IAMResources, error) { |
| 57 | + // Create IAM role with assume role policy for EKS pod identity |
| 58 | + assumeRolePolicy := IAMPolicy{ |
| 59 | + Version: "2012-10-17", |
| 60 | + Statement: []IAMStatement{ |
| 61 | + { |
| 62 | + Sid: "AllowEksAuthToAssumeRoleForPodIdentity", |
| 63 | + Effect: "Allow", |
| 64 | + Principal: struct { |
| 65 | + Service []string `json:"Service"` |
| 66 | + }{ |
| 67 | + Service: []string{ |
| 68 | + "pods.eks.amazonaws.com", |
| 69 | + "ec2.amazonaws.com", |
| 70 | + }, |
| 71 | + }, |
| 72 | + Action: []string{ |
| 73 | + "sts:AssumeRole", |
| 74 | + "sts:TagSession", |
| 75 | + }, |
| 76 | + }, |
| 77 | + }, |
| 78 | + } |
| 79 | + |
| 80 | + assumeRolePolicyJSON, err := json.Marshal(assumeRolePolicy) |
| 81 | + if err != nil { |
| 82 | + return nil, fmt.Errorf("failed to marshal assume role policy: %w", err) |
| 83 | + } |
| 84 | + |
| 85 | + role, err := iam.NewRole(ctx, fmt.Sprintf("%s-role", name), &iam.RoleArgs{ |
| 86 | + AssumeRolePolicy: pulumi.String(assumeRolePolicyJSON), |
| 87 | + Description: pulumi.String(fmt.Sprintf("Role for %s pod to assume", serviceName)), |
| 88 | + Tags: pulumi.StringMap{ |
| 89 | + "Name": pulumi.String(fmt.Sprintf("%s-role", name)), |
| 90 | + }, |
| 91 | + }, pulumi.Parent(parent)) |
| 92 | + if err != nil { |
| 93 | + return nil, fmt.Errorf("failed to create IAM role: %w", err) |
| 94 | + } |
| 95 | + |
| 96 | + // Create KMS policy for the specified key |
| 97 | + policyJSON := CreateKMSPolicy(keyArn) |
| 98 | + |
| 99 | + policy, err := iam.NewPolicy(ctx, fmt.Sprintf("%s-policy", name), &iam.PolicyArgs{ |
| 100 | + Policy: policyJSON, |
| 101 | + }, pulumi.Parent(parent)) |
| 102 | + if err != nil { |
| 103 | + return nil, fmt.Errorf("failed to create IAM policy: %w", err) |
| 104 | + } |
| 105 | + |
| 106 | + // Attach the KMS policy to the role |
| 107 | + policyAttachment, err := iam.NewRolePolicyAttachment(ctx, fmt.Sprintf("%s-role-policy-attachment", name), &iam.RolePolicyAttachmentArgs{ |
| 108 | + Role: role.Name, |
| 109 | + PolicyArn: policy.Arn, |
| 110 | + }, pulumi.Parent(parent)) |
| 111 | + if err != nil { |
| 112 | + return nil, fmt.Errorf("failed to attach policy to role: %w", err) |
| 113 | + } |
| 114 | + |
| 115 | + return &IAMResources{ |
| 116 | + Role: role, |
| 117 | + Policy: policy, |
| 118 | + PolicyAttachment: policyAttachment, |
| 119 | + }, nil |
| 120 | +} |
| 121 | + |
| 122 | +// CreateKMSPolicy creates a KMS policy document that grants permissions to sign messages |
| 123 | +// and retrieve public keys using the specified KMS key. |
| 124 | +// |
| 125 | +// Parameters: |
| 126 | +// - key: The ARN of the KMS key to create the policy for |
| 127 | +// |
| 128 | +// Returns: |
| 129 | +// - pulumi.StringOutput: A Pulumi output containing the JSON policy document |
| 130 | +// |
| 131 | +// The policy grants the following permissions: |
| 132 | +// - kms:Sign: Allows signing messages using the KMS key |
| 133 | +// - kms:GetPublicKey: Allows retrieving the public key associated with the KMS key |
| 134 | +func CreateKMSPolicy(key pulumi.StringInput) pulumi.StringOutput { |
| 135 | + policy := KMSPolicy{ |
| 136 | + Version: "2012-10-17", |
| 137 | + Statement: []KMSStatement{ |
| 138 | + { |
| 139 | + Effect: "Allow", |
| 140 | + Action: []string{ |
| 141 | + "kms:Sign", |
| 142 | + "kms:GetPublicKey", |
| 143 | + }, |
| 144 | + Resource: key, |
| 145 | + }, |
| 146 | + }, |
| 147 | + } |
| 148 | + |
| 149 | + // Convert to JSON string output |
| 150 | + return pulumi.All(key).ApplyT(func(_ []interface{}) (string, error) { |
| 151 | + jsonBytes, err := json.Marshal(policy) |
| 152 | + if err != nil { |
| 153 | + return "", err |
| 154 | + } |
| 155 | + return string(jsonBytes), nil |
| 156 | + }).(pulumi.StringOutput) |
| 157 | +} |
0 commit comments