Skip to content

Commit f9e9df6

Browse files
authored
feat: aws package, so builder and signet_node are isolated to k8s resources (#4)
1 parent c52fec4 commit f9e9df6

File tree

7 files changed

+247
-144
lines changed

7 files changed

+247
-144
lines changed

pkg/aws/iam.go

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
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+
}

pkg/aws/iam_test.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
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+
package aws
5+
6+
import (
7+
"testing"
8+
9+
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
10+
"github.com/stretchr/testify/assert"
11+
)
12+
13+
// TestCreateKMSPolicy tests the creation of KMS policies with different key ARNs.
14+
// Since we can't directly access the string value from StringOutput in a unit test,
15+
// we verify that the function returns non-nil values and that it properly handles
16+
// different input key ARNs.
17+
func TestCreateKMSPolicy(t *testing.T) {
18+
// Test with a simple key ARN
19+
keyArn := "arn:aws:kms:us-west-2:123456789012:key/1234abcd-12ab-34cd-56ef-1234567890ab"
20+
key := pulumi.String(keyArn)
21+
22+
// Get the policy string
23+
// Note: We can't directly access the string value from StringOutput in a unit test
24+
// So we'll just verify the function returns a non-nil value and the structure will
25+
// be tested separately when used in the actual builder component.
26+
policy := CreateKMSPolicy(key)
27+
28+
// We can only indirectly test this by asserting the output is not nil
29+
assert.NotNil(t, policy)
30+
31+
// Test with another key to ensure the function uses the provided key
32+
anotherKey := pulumi.String("another-key-arn")
33+
anotherPolicy := CreateKMSPolicy(anotherKey)
34+
assert.NotNil(t, anotherPolicy)
35+
}

pkg/aws/types.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
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+
package aws
5+
6+
import (
7+
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
8+
)
9+
10+
// IAMStatement represents a statement in an IAM policy document.
11+
// It defines a single permission statement that specifies what actions
12+
// are allowed or denied on which resources.
13+
type IAMStatement struct {
14+
// Sid is an optional identifier for the statement
15+
Sid string `json:"sid,omitempty"`
16+
// Effect specifies whether the statement allows or denies access
17+
Effect string `json:"effect"`
18+
// Principal specifies who is allowed or denied access
19+
Principal struct {
20+
// Service contains a list of AWS services that can assume this role
21+
Service []string `json:"Service"`
22+
} `json:"Principal"`
23+
// Action specifies the AWS actions that are allowed or denied
24+
Action []string `json:"Action"`
25+
}
26+
27+
// IAMPolicy represents a complete IAM policy document.
28+
// It contains a version and a list of statements that define the policy's permissions.
29+
type IAMPolicy struct {
30+
// Version specifies the policy language version
31+
Version string `json:"Version"`
32+
// Statement contains the list of permission statements
33+
Statement []IAMStatement `json:"Statement"`
34+
}
35+
36+
// KMSStatement represents a statement in a KMS policy document.
37+
// It defines permissions specifically for AWS KMS operations.
38+
type KMSStatement struct {
39+
// Effect specifies whether the statement allows or denies access
40+
Effect string `json:"Effect"`
41+
// Action specifies the KMS actions that are allowed or denied
42+
Action []string `json:"Action"`
43+
// Resource specifies the KMS key ARN that the permissions apply to
44+
Resource pulumi.StringInput `json:"Resource"`
45+
}
46+
47+
// KMSPolicy represents a complete KMS policy document.
48+
// It contains a version and a list of statements that define the KMS permissions.
49+
type KMSPolicy struct {
50+
// Version specifies the policy language version
51+
Version string `json:"Version"`
52+
// Statement contains the list of KMS permission statements
53+
Statement []KMSStatement `json:"Statement"`
54+
}

pkg/builder/builder.go

Lines changed: 1 addition & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,10 @@
22
package builder
33

44
import (
5-
"encoding/json"
65
"fmt"
76
"strconv"
87

98
"github.com/init4tech/signet-infra-components/pkg/utils"
10-
"github.com/pulumi/pulumi-aws/sdk/v6/go/aws/iam"
119
crd "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/apiextensions"
1210
appsv1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/apps/v1"
1311
corev1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/core/v1"
@@ -53,66 +51,6 @@ func NewBuilder(ctx *pulumi.Context, args BuilderComponentArgs, opts ...pulumi.R
5351
}
5452
component.ServiceAccount = sa
5553

56-
// Create IAM role
57-
assumeRolePolicy := IAMPolicy{
58-
Version: "2012-10-17",
59-
Statement: []IAMStatement{
60-
{
61-
Sid: "AllowEksAuthToAssumeRoleForPodIdentity",
62-
Effect: "Allow",
63-
Principal: struct {
64-
Service []string `json:"Service"`
65-
}{
66-
Service: []string{
67-
"pods.eks.amazonaws.com",
68-
"ec2.amazonaws.com",
69-
},
70-
},
71-
Action: []string{
72-
"sts:AssumeRole",
73-
"sts:TagSession",
74-
},
75-
},
76-
},
77-
}
78-
79-
assumeRolePolicyJSON, err := json.Marshal(assumeRolePolicy)
80-
if err != nil {
81-
return nil, fmt.Errorf("failed to marshal assume role policy: %w", err)
82-
}
83-
84-
role, err := iam.NewRole(ctx, fmt.Sprintf("%s-role", args.Name), &iam.RoleArgs{
85-
AssumeRolePolicy: pulumi.String(assumeRolePolicyJSON),
86-
Description: pulumi.String(fmt.Sprintf("Role for %s pod to assume", args.Name)),
87-
Tags: pulumi.StringMap{
88-
"Name": pulumi.String(fmt.Sprintf("%s-role", args.Name)),
89-
},
90-
}, pulumi.Parent(component))
91-
if err != nil {
92-
return nil, fmt.Errorf("failed to create IAM role: %w", err)
93-
}
94-
component.IAMRole = role
95-
96-
// Create KMS policy
97-
policyJSON := CreateKMSPolicy(args.BuilderEnv.BuilderKey)
98-
99-
policy, err := iam.NewPolicy(ctx, fmt.Sprintf("%s-policy", args.Name), &iam.PolicyArgs{
100-
Policy: policyJSON,
101-
}, pulumi.Parent(component))
102-
if err != nil {
103-
return nil, fmt.Errorf("failed to create IAM policy: %w", err)
104-
}
105-
component.IAMPolicy = policy
106-
107-
// Attach policy to role
108-
_, err = iam.NewRolePolicyAttachment(ctx, fmt.Sprintf("%s-role-policy-attachment", args.Name), &iam.RolePolicyAttachmentArgs{
109-
Role: role.Name,
110-
PolicyArn: policy.Arn,
111-
}, pulumi.Parent(component))
112-
if err != nil {
113-
return nil, fmt.Errorf("failed to attach policy to role: %w", err)
114-
}
115-
11654
// Create ConfigMap for environment variables
11755
configMap, err := utils.CreateConfigMap(
11856
ctx,
@@ -199,7 +137,7 @@ func NewBuilder(ctx *pulumi.Context, args BuilderComponentArgs, opts ...pulumi.R
199137
},
200138
},
201139
},
202-
}, pulumi.DependsOn([]pulumi.Resource{role, policy}), pulumi.DeleteBeforeReplace(true), pulumi.Parent(component))
140+
}, pulumi.DeleteBeforeReplace(true), pulumi.Parent(component))
203141
if err != nil {
204142
return nil, fmt.Errorf("failed to create deployment: %w", err)
205143
}

pkg/builder/helpers.go

Lines changed: 0 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1 @@
11
package builder
2-
3-
import (
4-
"encoding/json"
5-
6-
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
7-
)
8-
9-
// CreateKMSPolicy creates a KMS policy for the builder service.
10-
// Exported for testing.
11-
func CreateKMSPolicy(key pulumi.StringInput) pulumi.StringOutput {
12-
policy := KMSPolicy{
13-
Version: "2012-10-17",
14-
Statement: []KMSStatement{
15-
{
16-
Effect: "Allow",
17-
Action: []string{
18-
"kms:Sign",
19-
"kms:GetPublicKey",
20-
},
21-
Resource: key,
22-
},
23-
},
24-
}
25-
26-
// Convert to JSON string output
27-
return pulumi.All(key).ApplyT(func(_ []interface{}) (string, error) {
28-
jsonBytes, err := json.Marshal(policy)
29-
if err != nil {
30-
return "", err
31-
}
32-
return string(jsonBytes), nil
33-
}).(pulumi.StringOutput)
34-
}

pkg/builder/helpers_test.go

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,26 +7,6 @@ import (
77
"github.com/stretchr/testify/assert"
88
)
99

10-
func TestCreateKMSPolicy(t *testing.T) {
11-
// Test with a simple key ARN
12-
keyArn := "arn:aws:kms:us-west-2:123456789012:key/1234abcd-12ab-34cd-56ef-1234567890ab"
13-
key := pulumi.String(keyArn)
14-
15-
// Get the policy string
16-
// Note: We can't directly access the string value from StringOutput in a unit test
17-
// So we'll just verify the function returns a non-nil value and the structure will
18-
// be tested separately when used in the actual builder component.
19-
policy := CreateKMSPolicy(key)
20-
21-
// We can only indirectly test this by asserting the output is not nil
22-
assert.NotNil(t, policy)
23-
24-
// Test with another key to ensure the function uses the provided key
25-
anotherKey := pulumi.String("another-key-arn")
26-
anotherPolicy := CreateKMSPolicy(anotherKey)
27-
assert.NotNil(t, anotherPolicy)
28-
}
29-
3010
func TestBuilderEnvGetEnvMap(t *testing.T) {
3111
// Create a test BuilderEnv with some values
3212
env := BuilderEnv{

0 commit comments

Comments
 (0)