From fca18816b4f665596d17f7315684959f7ce9d0cf Mon Sep 17 00:00:00 2001 From: Swanny Date: Tue, 20 May 2025 15:12:23 -0400 Subject: [PATCH 1/2] feat: aws package, so builder and signet_node are isolated to k8s resources --- pkg/aws/iam.go | 115 ++++++++++++++++++++++++++++++++++++ pkg/aws/iam_test.go | 28 +++++++++ pkg/aws/types.go | 34 +++++++++++ pkg/builder/builder.go | 64 +------------------- pkg/builder/helpers.go | 33 ----------- pkg/builder/helpers_test.go | 20 ------- pkg/builder/types.go | 28 --------- 7 files changed, 178 insertions(+), 144 deletions(-) create mode 100644 pkg/aws/iam.go create mode 100644 pkg/aws/iam_test.go create mode 100644 pkg/aws/types.go diff --git a/pkg/aws/iam.go b/pkg/aws/iam.go new file mode 100644 index 0000000..e512701 --- /dev/null +++ b/pkg/aws/iam.go @@ -0,0 +1,115 @@ +package aws + +import ( + "encoding/json" + "fmt" + + "github.com/pulumi/pulumi-aws/sdk/v6/go/aws/iam" + "github.com/pulumi/pulumi/sdk/v3/go/pulumi" +) + +// IAMResources contains AWS IAM resources created for a component +type IAMResources struct { + Role *iam.Role + Policy *iam.Policy + PolicyAttachment *iam.RolePolicyAttachment +} + +// CreateIAMResources creates IAM resources (role, policy, and policy attachment) for a component +func CreateIAMResources( + ctx *pulumi.Context, + name string, + serviceName string, + keyArn pulumi.StringInput, + parent pulumi.Resource, +) (*IAMResources, error) { + // Create IAM role + assumeRolePolicy := IAMPolicy{ + Version: "2012-10-17", + Statement: []IAMStatement{ + { + Sid: "AllowEksAuthToAssumeRoleForPodIdentity", + Effect: "Allow", + Principal: struct { + Service []string `json:"Service"` + }{ + Service: []string{ + "pods.eks.amazonaws.com", + "ec2.amazonaws.com", + }, + }, + Action: []string{ + "sts:AssumeRole", + "sts:TagSession", + }, + }, + }, + } + + assumeRolePolicyJSON, err := json.Marshal(assumeRolePolicy) + if err != nil { + return nil, fmt.Errorf("failed to marshal assume role policy: %w", err) + } + + role, err := iam.NewRole(ctx, fmt.Sprintf("%s-role", name), &iam.RoleArgs{ + AssumeRolePolicy: pulumi.String(assumeRolePolicyJSON), + Description: pulumi.String(fmt.Sprintf("Role for %s pod to assume", serviceName)), + Tags: pulumi.StringMap{ + "Name": pulumi.String(fmt.Sprintf("%s-role", name)), + }, + }, pulumi.Parent(parent)) + if err != nil { + return nil, fmt.Errorf("failed to create IAM role: %w", err) + } + + // Create KMS policy + policyJSON := CreateKMSPolicy(keyArn) + + policy, err := iam.NewPolicy(ctx, fmt.Sprintf("%s-policy", name), &iam.PolicyArgs{ + Policy: policyJSON, + }, pulumi.Parent(parent)) + if err != nil { + return nil, fmt.Errorf("failed to create IAM policy: %w", err) + } + + // Attach policy to role + policyAttachment, err := iam.NewRolePolicyAttachment(ctx, fmt.Sprintf("%s-role-policy-attachment", name), &iam.RolePolicyAttachmentArgs{ + Role: role.Name, + PolicyArn: policy.Arn, + }, pulumi.Parent(parent)) + if err != nil { + return nil, fmt.Errorf("failed to attach policy to role: %w", err) + } + + return &IAMResources{ + Role: role, + Policy: policy, + PolicyAttachment: policyAttachment, + }, nil +} + +// CreateKMSPolicy creates a KMS policy for the given key +func CreateKMSPolicy(key pulumi.StringInput) pulumi.StringOutput { + policy := KMSPolicy{ + Version: "2012-10-17", + Statement: []KMSStatement{ + { + Effect: "Allow", + Action: []string{ + "kms:Sign", + "kms:GetPublicKey", + }, + Resource: key, + }, + }, + } + + // Convert to JSON string output + return pulumi.All(key).ApplyT(func(_ []interface{}) (string, error) { + jsonBytes, err := json.Marshal(policy) + if err != nil { + return "", err + } + return string(jsonBytes), nil + }).(pulumi.StringOutput) +} diff --git a/pkg/aws/iam_test.go b/pkg/aws/iam_test.go new file mode 100644 index 0000000..91d7479 --- /dev/null +++ b/pkg/aws/iam_test.go @@ -0,0 +1,28 @@ +package aws + +import ( + "testing" + + "github.com/pulumi/pulumi/sdk/v3/go/pulumi" + "github.com/stretchr/testify/assert" +) + +func TestCreateKMSPolicy(t *testing.T) { + // Test with a simple key ARN + keyArn := "arn:aws:kms:us-west-2:123456789012:key/1234abcd-12ab-34cd-56ef-1234567890ab" + key := pulumi.String(keyArn) + + // Get the policy string + // Note: We can't directly access the string value from StringOutput in a unit test + // So we'll just verify the function returns a non-nil value and the structure will + // be tested separately when used in the actual builder component. + policy := CreateKMSPolicy(key) + + // We can only indirectly test this by asserting the output is not nil + assert.NotNil(t, policy) + + // Test with another key to ensure the function uses the provided key + anotherKey := pulumi.String("another-key-arn") + anotherPolicy := CreateKMSPolicy(anotherKey) + assert.NotNil(t, anotherPolicy) +} diff --git a/pkg/aws/types.go b/pkg/aws/types.go new file mode 100644 index 0000000..fb0dd5c --- /dev/null +++ b/pkg/aws/types.go @@ -0,0 +1,34 @@ +package aws + +import ( + "github.com/pulumi/pulumi/sdk/v3/go/pulumi" +) + +// IAMStatement represents a statement in an IAM policy +type IAMStatement struct { + Sid string `json:"sid,omitempty"` + Effect string `json:"effect"` + Principal struct { + Service []string `json:"Service"` + } `json:"Principal"` + Action []string `json:"Action"` +} + +// IAMPolicy represents an IAM policy document +type IAMPolicy struct { + Version string `json:"Version"` + Statement []IAMStatement `json:"Statement"` +} + +// KMSStatement represents a statement in a KMS policy +type KMSStatement struct { + Effect string `json:"Effect"` + Action []string `json:"Action"` + Resource pulumi.StringInput `json:"Resource"` +} + +// KMSPolicy represents a KMS policy document +type KMSPolicy struct { + Version string `json:"Version"` + Statement []KMSStatement `json:"Statement"` +} diff --git a/pkg/builder/builder.go b/pkg/builder/builder.go index 323f60e..168b3a4 100644 --- a/pkg/builder/builder.go +++ b/pkg/builder/builder.go @@ -2,12 +2,10 @@ package builder import ( - "encoding/json" "fmt" "strconv" "github.com/init4tech/signet-infra-components/pkg/utils" - "github.com/pulumi/pulumi-aws/sdk/v6/go/aws/iam" crd "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/apiextensions" appsv1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/apps/v1" 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 } component.ServiceAccount = sa - // Create IAM role - assumeRolePolicy := IAMPolicy{ - Version: "2012-10-17", - Statement: []IAMStatement{ - { - Sid: "AllowEksAuthToAssumeRoleForPodIdentity", - Effect: "Allow", - Principal: struct { - Service []string `json:"Service"` - }{ - Service: []string{ - "pods.eks.amazonaws.com", - "ec2.amazonaws.com", - }, - }, - Action: []string{ - "sts:AssumeRole", - "sts:TagSession", - }, - }, - }, - } - - assumeRolePolicyJSON, err := json.Marshal(assumeRolePolicy) - if err != nil { - return nil, fmt.Errorf("failed to marshal assume role policy: %w", err) - } - - role, err := iam.NewRole(ctx, fmt.Sprintf("%s-role", args.Name), &iam.RoleArgs{ - AssumeRolePolicy: pulumi.String(assumeRolePolicyJSON), - Description: pulumi.String(fmt.Sprintf("Role for %s pod to assume", args.Name)), - Tags: pulumi.StringMap{ - "Name": pulumi.String(fmt.Sprintf("%s-role", args.Name)), - }, - }, pulumi.Parent(component)) - if err != nil { - return nil, fmt.Errorf("failed to create IAM role: %w", err) - } - component.IAMRole = role - - // Create KMS policy - policyJSON := CreateKMSPolicy(args.BuilderEnv.BuilderKey) - - policy, err := iam.NewPolicy(ctx, fmt.Sprintf("%s-policy", args.Name), &iam.PolicyArgs{ - Policy: policyJSON, - }, pulumi.Parent(component)) - if err != nil { - return nil, fmt.Errorf("failed to create IAM policy: %w", err) - } - component.IAMPolicy = policy - - // Attach policy to role - _, err = iam.NewRolePolicyAttachment(ctx, fmt.Sprintf("%s-role-policy-attachment", args.Name), &iam.RolePolicyAttachmentArgs{ - Role: role.Name, - PolicyArn: policy.Arn, - }, pulumi.Parent(component)) - if err != nil { - return nil, fmt.Errorf("failed to attach policy to role: %w", err) - } - // Create ConfigMap for environment variables configMap, err := utils.CreateConfigMap( ctx, @@ -199,7 +137,7 @@ func NewBuilder(ctx *pulumi.Context, args BuilderComponentArgs, opts ...pulumi.R }, }, }, - }, pulumi.DependsOn([]pulumi.Resource{role, policy}), pulumi.DeleteBeforeReplace(true), pulumi.Parent(component)) + }, pulumi.DeleteBeforeReplace(true), pulumi.Parent(component)) if err != nil { return nil, fmt.Errorf("failed to create deployment: %w", err) } diff --git a/pkg/builder/helpers.go b/pkg/builder/helpers.go index a8b8c0d..e647fb2 100644 --- a/pkg/builder/helpers.go +++ b/pkg/builder/helpers.go @@ -1,34 +1 @@ package builder - -import ( - "encoding/json" - - "github.com/pulumi/pulumi/sdk/v3/go/pulumi" -) - -// CreateKMSPolicy creates a KMS policy for the builder service. -// Exported for testing. -func CreateKMSPolicy(key pulumi.StringInput) pulumi.StringOutput { - policy := KMSPolicy{ - Version: "2012-10-17", - Statement: []KMSStatement{ - { - Effect: "Allow", - Action: []string{ - "kms:Sign", - "kms:GetPublicKey", - }, - Resource: key, - }, - }, - } - - // Convert to JSON string output - return pulumi.All(key).ApplyT(func(_ []interface{}) (string, error) { - jsonBytes, err := json.Marshal(policy) - if err != nil { - return "", err - } - return string(jsonBytes), nil - }).(pulumi.StringOutput) -} diff --git a/pkg/builder/helpers_test.go b/pkg/builder/helpers_test.go index 1b5c309..b378ff1 100644 --- a/pkg/builder/helpers_test.go +++ b/pkg/builder/helpers_test.go @@ -7,26 +7,6 @@ import ( "github.com/stretchr/testify/assert" ) -func TestCreateKMSPolicy(t *testing.T) { - // Test with a simple key ARN - keyArn := "arn:aws:kms:us-west-2:123456789012:key/1234abcd-12ab-34cd-56ef-1234567890ab" - key := pulumi.String(keyArn) - - // Get the policy string - // Note: We can't directly access the string value from StringOutput in a unit test - // So we'll just verify the function returns a non-nil value and the structure will - // be tested separately when used in the actual builder component. - policy := CreateKMSPolicy(key) - - // We can only indirectly test this by asserting the output is not nil - assert.NotNil(t, policy) - - // Test with another key to ensure the function uses the provided key - anotherKey := pulumi.String("another-key-arn") - anotherPolicy := CreateKMSPolicy(anotherKey) - assert.NotNil(t, anotherPolicy) -} - func TestBuilderEnvGetEnvMap(t *testing.T) { // Create a test BuilderEnv with some values env := BuilderEnv{ diff --git a/pkg/builder/types.go b/pkg/builder/types.go index dd36c97..c72041d 100644 --- a/pkg/builder/types.go +++ b/pkg/builder/types.go @@ -2,7 +2,6 @@ package builder import ( "github.com/init4tech/signet-infra-components/pkg/utils" - "github.com/pulumi/pulumi-aws/sdk/v6/go/aws/iam" appsv1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/apps/v1" corev1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/core/v1" "github.com/pulumi/pulumi/sdk/v3/go/pulumi" @@ -28,8 +27,6 @@ type BuilderComponent struct { Deployment *appsv1.Deployment Service *corev1.Service ServiceAccount *corev1.ServiceAccount - IAMRole *iam.Role - IAMPolicy *iam.Policy ConfigMap *corev1.ConfigMap } @@ -89,31 +86,6 @@ func (e BuilderEnv) GetEnvMap() pulumi.StringMap { return utils.CreateEnvMap(e) } -type IAMStatement struct { - Sid string `json:"sid,omitempty"` - Effect string `json:"effect"` - Principal struct { - Service []string `json:"Service"` - } `json:"Principal"` - Action []string `json:"Action"` -} - -type IAMPolicy struct { - Version string `json:"Version"` - Statement []IAMStatement `json:"Statement"` -} - -type KMSStatement struct { - Effect string `json:"Effect"` - Action []string `json:"Action"` - Resource pulumi.StringInput `json:"Resource"` -} - -type KMSPolicy struct { - Version string `json:"Version"` - Statement []KMSStatement `json:"Statement"` -} - type Builder interface { GetServiceURL() pulumi.StringOutput GetMetricsURL() pulumi.StringOutput From 1d0d1653cc024d18166ca5ae5eadd972e49c87fb Mon Sep 17 00:00:00 2001 From: Swanny Date: Tue, 27 May 2025 12:34:48 -0400 Subject: [PATCH 2/2] fix: add doc comments --- pkg/aws/iam.go | 58 ++++++++++++++++++++++++++++++++++++++------- pkg/aws/iam_test.go | 7 ++++++ pkg/aws/types.go | 40 +++++++++++++++++++++++-------- 3 files changed, 87 insertions(+), 18 deletions(-) diff --git a/pkg/aws/iam.go b/pkg/aws/iam.go index e512701..9db6d43 100644 --- a/pkg/aws/iam.go +++ b/pkg/aws/iam.go @@ -1,3 +1,10 @@ +// Package aws provides utilities for creating and managing AWS IAM resources +// for Kubernetes workloads running in EKS. It handles the creation of IAM roles, +// policies, and policy attachments that enable pod identity and KMS access. +// +// The package is designed to work with Pulumi and provides a high-level interface +// for managing AWS IAM resources, particularly for services that need to interact +// with AWS KMS for cryptographic operations. package aws import ( @@ -8,14 +15,38 @@ import ( "github.com/pulumi/pulumi/sdk/v3/go/pulumi" ) -// IAMResources contains AWS IAM resources created for a component +// IAMResources contains AWS IAM resources created for a component. +// These resources work together to provide the necessary permissions +// for a Kubernetes workload to interact with AWS services. type IAMResources struct { - Role *iam.Role - Policy *iam.Policy + // Role is the IAM role that can be assumed by the Kubernetes workload + Role *iam.Role + // Policy defines the permissions granted to the role + Policy *iam.Policy + // PolicyAttachment connects the policy to the role PolicyAttachment *iam.RolePolicyAttachment } -// CreateIAMResources creates IAM resources (role, policy, and policy attachment) for a component +// CreateIAMResources creates IAM resources (role, policy, and policy attachment) for a component. +// It sets up the necessary permissions for a Kubernetes workload to interact with AWS KMS. +// +// Parameters: +// - ctx: The Pulumi context +// - name: Base name for the IAM resources +// - serviceName: Name of the service that will use these IAM resources +// - keyArn: ARN of the KMS key that the service needs to access +// - parent: Parent Pulumi resource for dependency tracking +// +// Returns: +// - *IAMResources: The created IAM resources +// - error: Any error that occurred during creation +// +// Example: +// +// resources, err := CreateIAMResources(ctx, "my-service", "my-service", keyArn, parent) +// if err != nil { +// return nil, fmt.Errorf("failed to create IAM resources: %w", err) +// } func CreateIAMResources( ctx *pulumi.Context, name string, @@ -23,7 +54,7 @@ func CreateIAMResources( keyArn pulumi.StringInput, parent pulumi.Resource, ) (*IAMResources, error) { - // Create IAM role + // Create IAM role with assume role policy for EKS pod identity assumeRolePolicy := IAMPolicy{ Version: "2012-10-17", Statement: []IAMStatement{ @@ -62,7 +93,7 @@ func CreateIAMResources( return nil, fmt.Errorf("failed to create IAM role: %w", err) } - // Create KMS policy + // Create KMS policy for the specified key policyJSON := CreateKMSPolicy(keyArn) policy, err := iam.NewPolicy(ctx, fmt.Sprintf("%s-policy", name), &iam.PolicyArgs{ @@ -72,7 +103,7 @@ func CreateIAMResources( return nil, fmt.Errorf("failed to create IAM policy: %w", err) } - // Attach policy to role + // Attach the KMS policy to the role policyAttachment, err := iam.NewRolePolicyAttachment(ctx, fmt.Sprintf("%s-role-policy-attachment", name), &iam.RolePolicyAttachmentArgs{ Role: role.Name, PolicyArn: policy.Arn, @@ -88,7 +119,18 @@ func CreateIAMResources( }, nil } -// CreateKMSPolicy creates a KMS policy for the given key +// CreateKMSPolicy creates a KMS policy document that grants permissions to sign messages +// and retrieve public keys using the specified KMS key. +// +// Parameters: +// - key: The ARN of the KMS key to create the policy for +// +// Returns: +// - pulumi.StringOutput: A Pulumi output containing the JSON policy document +// +// The policy grants the following permissions: +// - kms:Sign: Allows signing messages using the KMS key +// - kms:GetPublicKey: Allows retrieving the public key associated with the KMS key func CreateKMSPolicy(key pulumi.StringInput) pulumi.StringOutput { policy := KMSPolicy{ Version: "2012-10-17", diff --git a/pkg/aws/iam_test.go b/pkg/aws/iam_test.go index 91d7479..f029ce7 100644 --- a/pkg/aws/iam_test.go +++ b/pkg/aws/iam_test.go @@ -1,3 +1,6 @@ +// Package aws provides utilities for creating and managing AWS IAM resources +// for Kubernetes workloads running in EKS. It handles the creation of IAM roles, +// policies, and policy attachments that enable pod identity and KMS access. package aws import ( @@ -7,6 +10,10 @@ import ( "github.com/stretchr/testify/assert" ) +// TestCreateKMSPolicy tests the creation of KMS policies with different key ARNs. +// Since we can't directly access the string value from StringOutput in a unit test, +// we verify that the function returns non-nil values and that it properly handles +// different input key ARNs. func TestCreateKMSPolicy(t *testing.T) { // Test with a simple key ARN keyArn := "arn:aws:kms:us-west-2:123456789012:key/1234abcd-12ab-34cd-56ef-1234567890ab" diff --git a/pkg/aws/types.go b/pkg/aws/types.go index fb0dd5c..a000659 100644 --- a/pkg/aws/types.go +++ b/pkg/aws/types.go @@ -1,34 +1,54 @@ +// Package aws provides utilities for creating and managing AWS IAM resources +// for Kubernetes workloads running in EKS. It handles the creation of IAM roles, +// policies, and policy attachments that enable pod identity and KMS access. package aws import ( "github.com/pulumi/pulumi/sdk/v3/go/pulumi" ) -// IAMStatement represents a statement in an IAM policy +// IAMStatement represents a statement in an IAM policy document. +// It defines a single permission statement that specifies what actions +// are allowed or denied on which resources. type IAMStatement struct { - Sid string `json:"sid,omitempty"` - Effect string `json:"effect"` + // Sid is an optional identifier for the statement + Sid string `json:"sid,omitempty"` + // Effect specifies whether the statement allows or denies access + Effect string `json:"effect"` + // Principal specifies who is allowed or denied access Principal struct { + // Service contains a list of AWS services that can assume this role Service []string `json:"Service"` } `json:"Principal"` + // Action specifies the AWS actions that are allowed or denied Action []string `json:"Action"` } -// IAMPolicy represents an IAM policy document +// IAMPolicy represents a complete IAM policy document. +// It contains a version and a list of statements that define the policy's permissions. type IAMPolicy struct { - Version string `json:"Version"` + // Version specifies the policy language version + Version string `json:"Version"` + // Statement contains the list of permission statements Statement []IAMStatement `json:"Statement"` } -// KMSStatement represents a statement in a KMS policy +// KMSStatement represents a statement in a KMS policy document. +// It defines permissions specifically for AWS KMS operations. type KMSStatement struct { - Effect string `json:"Effect"` - Action []string `json:"Action"` + // Effect specifies whether the statement allows or denies access + Effect string `json:"Effect"` + // Action specifies the KMS actions that are allowed or denied + Action []string `json:"Action"` + // Resource specifies the KMS key ARN that the permissions apply to Resource pulumi.StringInput `json:"Resource"` } -// KMSPolicy represents a KMS policy document +// KMSPolicy represents a complete KMS policy document. +// It contains a version and a list of statements that define the KMS permissions. type KMSPolicy struct { - Version string `json:"Version"` + // Version specifies the policy language version + Version string `json:"Version"` + // Statement contains the list of KMS permission statements Statement []KMSStatement `json:"Statement"` }