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 8bd3827..44a8e70 100644 --- a/pkg/builder/builder.go +++ b/pkg/builder/builder.go @@ -2,11 +2,9 @@ package builder import ( - "encoding/json" "fmt" "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" @@ -40,66 +38,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, @@ -210,7 +148,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