Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Tighter IAM policies #612

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions pkg/cloud/aws/services/cloudformation/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ go_library(
deps = [
"//pkg/cloud/aws/services/awserrors:go_default_library",
"//pkg/cloud/aws/services/iam:go_default_library",
"//pkg/cloud/aws/tags:go_default_library",
"//vendor/github.com/aws/aws-sdk-go/aws:go_default_library",
"//vendor/github.com/aws/aws-sdk-go/service/cloudformation:go_default_library",
"//vendor/github.com/aws/aws-sdk-go/service/cloudformation/cloudformationiface:go_default_library",
Expand Down
185 changes: 165 additions & 20 deletions pkg/cloud/aws/services/cloudformation/bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,24 +27,34 @@ import (

"sigs.k8s.io/cluster-api-provider-aws/pkg/cloud/aws/services/awserrors"
"sigs.k8s.io/cluster-api-provider-aws/pkg/cloud/aws/services/iam"
"sigs.k8s.io/cluster-api-provider-aws/pkg/cloud/aws/tags"
)

const (
ControllersPolicy = "AWSIAMManagedPolicyControllers"
// ControllersPolicy is the IAM Managed Policy for Cluster API AWS Controllers
ControllersPolicy = "AWSIAMManagedPolicyControllers"
// ControlPlanePolicy is the IAM Managed Policy for master nodes
ControlPlanePolicy = "AWSIAMManagedPolicyCloudProviderControlPlane"
NodePolicy = "AWSIAMManagedPolicyCloudProviderNodes"
// NodePolicy is the IAM Managed Policy for worker nodes
NodePolicy = "AWSIAMManagedPolicyCloudProviderNodes"
// CloudProvider is the resource prefix for AWS Cloud Provider related resources
CloudProvider = "cloud-provider-aws.k8s.io"
)

// ManagedIAMPolicyNames slice of managed IAM policies
var ManagedIAMPolicyNames = [...]string{ControllersPolicy, ControlPlanePolicy, NodePolicy}

// DefaultPath is the IAM path to be used
var DefaultPath = "/" + tags.NameAWSProviderPrefix

// BootstrapTemplate is an AWS CloudFormation template to bootstrap
// IAM policies, users and roles for use by Cluster API Provider AWS
func BootstrapTemplate(accountID string) *cloudformation.Template {
template := cloudformation.NewTemplate()

template.Resources[ControllersPolicy] = cloudformation.AWSIAMManagedPolicy{
ManagedPolicyName: iam.NewManagedName("controllers"),
Path: DefaultPath,
Description: `For the Kubernetes Cluster API Provider AWS Controllers`,
PolicyDocument: controllersPolicy(accountID),
Groups: []string{
Expand All @@ -57,7 +67,8 @@ func BootstrapTemplate(accountID string) *cloudformation.Template {
}

template.Resources[ControlPlanePolicy] = cloudformation.AWSIAMManagedPolicy{
ManagedPolicyName: iam.NewManagedName("control-plane"),
ManagedPolicyName: NewCloudProviderManagedName("control-plane"),
Path: DefaultPath,
Description: `For the Kubernetes Cloud Provider AWS Control Plane`,
PolicyDocument: cloudProviderControlPlaneAwsPolicy(),
Roles: []string{
Expand All @@ -66,7 +77,8 @@ func BootstrapTemplate(accountID string) *cloudformation.Template {
}

template.Resources[NodePolicy] = cloudformation.AWSIAMManagedPolicy{
ManagedPolicyName: iam.NewManagedName("nodes"),
ManagedPolicyName: NewCloudProviderManagedName("nodes"),
Path: DefaultPath,
Description: `For the Kubernetes Cloud Provider AWS nodes`,
PolicyDocument: cloudProviderNodeAwsPolicy(),
Roles: []string{
Expand All @@ -77,46 +89,54 @@ func BootstrapTemplate(accountID string) *cloudformation.Template {

template.Resources["AWSIAMUserBootstrapper"] = cloudformation.AWSIAMUser{
UserName: iam.NewManagedName("bootstrapper"),
Path: DefaultPath,
Groups: []string{
cloudformation.Ref("AWSIAMGroupBootstrapper"),
},
}

template.Resources["AWSIAMGroupBootstrapper"] = cloudformation.AWSIAMGroup{
GroupName: iam.NewManagedName("bootstrapper"),
Path: DefaultPath,
}

template.Resources["AWSIAMRoleControlPlane"] = cloudformation.AWSIAMRole{
RoleName: iam.NewManagedName("control-plane"),
Path: DefaultPath,
AssumeRolePolicyDocument: ec2AssumeRolePolicy(),
}

template.Resources["AWSIAMRoleControllers"] = cloudformation.AWSIAMRole{
RoleName: iam.NewManagedName("controllers"),
Path: DefaultPath,
AssumeRolePolicyDocument: ec2AssumeRolePolicy(),
}

template.Resources["AWSIAMRoleNodes"] = cloudformation.AWSIAMRole{
RoleName: iam.NewManagedName("nodes"),
Path: DefaultPath,
AssumeRolePolicyDocument: ec2AssumeRolePolicy(),
}

template.Resources["AWSIAMInstanceProfileControlPlane"] = cloudformation.AWSIAMInstanceProfile{
InstanceProfileName: iam.NewManagedName("control-plane"),
Path: DefaultPath,
Roles: []string{
cloudformation.Ref("AWSIAMRoleControlPlane"),
},
}

template.Resources["AWSIAMInstanceProfileControllers"] = cloudformation.AWSIAMInstanceProfile{
InstanceProfileName: iam.NewManagedName("controllers"),
Path: DefaultPath,
Roles: []string{
cloudformation.Ref("AWSIAMRoleControllers"),
},
}

template.Resources["AWSIAMInstanceProfileNodes"] = cloudformation.AWSIAMInstanceProfile{
InstanceProfileName: iam.NewManagedName("nodes"),
Path: DefaultPath,
Roles: []string{
cloudformation.Ref("AWSIAMRoleNodes"),
},
Expand All @@ -138,6 +158,93 @@ func ec2AssumeRolePolicy() *iam.PolicyDocument {
}
}

// NewCloudProviderManagedName creates an IAM acceptable name suffixed with
// the AWS Cloud Provider suffix
func NewCloudProviderManagedName(prefix string) string {
return fmt.Sprintf("%s.%s", prefix, CloudProvider)
}

func tagRequestStringEqualityConditions() map[string][]string {
return map[string][]string{
fmt.Sprintf("aws:RequestTag/%s", tags.NameAWSProviderManaged): {"true"},
fmt.Sprintf("aws:RequestTag/%s", tags.NameAWSClusterAPIRole): {
tags.ValueAPIServerRole,
tags.ValueBastionRole,
tags.ValueCommonRole,
tags.ValuePrivateRole,
tags.ValuePublicRole,
},
}
}

func tagMandatoryPresenceConditions() map[string][]string {
return map[string][]string{
"aws:TagKeys": {
fmt.Sprintf("%s*", tags.NameKubernetesClusterPrefix),
tags.NameAWSProviderManaged,
tags.NameAWSClusterAPIRole,
"Name",
},
}
}

func tagExistingObjectPresenceConditions(service string) map[string][]string {
return map[string][]string{
fmt.Sprintf("%s:ResourceTag/%s", service, tags.NameAWSProviderManaged): {"true"},
}
}

func tagRequestForNewObjectConditions() *iam.Conditions {
return &iam.Conditions{
"StringEquals": tagRequestStringEqualityConditions(),
"ForAnyValue:StringLike": tagMandatoryPresenceConditions(),
"Bool": secureTransportCondition(),
}
}

func taggedObjectRequestCondition(service string) *iam.Conditions {
return &iam.Conditions{
"StringEquals": tagExistingObjectPresenceConditions(service),
"Bool": secureTransportCondition(),
}
}

func tagRequestForExistingObjectConditions(service string) *iam.Conditions {
equalityTests := tagExistingObjectPresenceConditions(service)
for k, v := range tagRequestStringEqualityConditions() {
equalityTests[k] = v
}
return &iam.Conditions{
"StringEquals": equalityTests,
"ForAnyValue:StringLike": tagMandatoryPresenceConditions(),
"Bool": secureTransportCondition(),
}
}

func secureTransportCondition() map[string][]string {
return map[string][]string{"aws:SecureTransport": {"true"}}
}

func resourceNamespace(resourceType string, accountID string) string {
return fmt.Sprintf(
"arn:aws:iam::%s:%s/%s*",
resourceType,
accountID,
tags.NameAWSProviderPrefix,
)
}

func ec2RunInstanceConditions(accountID string) *iam.Conditions {
return &iam.Conditions{
"StringEquals": tagRequestStringEqualityConditions(),
"StringLike": map[string]string{
"ec2:InstanceProfile": resourceNamespace(accountID, "instance-profile"),
},
"ForAnyValue:StringLike": tagMandatoryPresenceConditions(),
"Bool": secureTransportCondition(),
}
}

func controllersPolicy(accountID string) *iam.PolicyDocument {
return &iam.PolicyDocument{
Version: iam.CurrentVersion,
Expand All @@ -156,7 +263,6 @@ func controllersPolicy(accountID string) *iam.PolicyDocument {
"ec2:CreateRouteTable",
"ec2:CreateSecurityGroup",
"ec2:CreateSubnet",
"ec2:CreateTags",
"ec2:CreateVpc",
"ec2:DeleteInternetGateway",
"ec2:DeleteNatGateway",
Expand All @@ -166,7 +272,6 @@ func controllersPolicy(accountID string) *iam.PolicyDocument {
"ec2:DeleteVpc",
"ec2:DescribeAddresses",
"ec2:DescribeAvailabilityZones",
"ec2:DescribeInstances",
"ec2:DescribeInternetGateways",
"ec2:DescribeImages",
"ec2:DescribeNatGateways",
Expand All @@ -179,16 +284,8 @@ func controllersPolicy(accountID string) *iam.PolicyDocument {
"ec2:ModifySubnetAttribute",
"ec2:ReleaseAddress",
"ec2:RevokeSecurityGroupIngress",
"ec2:RunInstances",
"ec2:TerminateInstances",
"elasticloadbalancing:CreateLoadBalancer",
"elasticloadbalancing:ConfigureHealthCheck",
"elasticloadbalancing:DeleteLoadBalancer",
"elasticloadbalancing:DescribeLoadBalancers",
"elasticloadbalancing:DescribeLoadBalancerAttributes",
"elasticloadbalancing:ModifyLoadBalancerAttributes",
"elasticloadbalancing:RegisterInstancesWithLoadBalancer",
},
Condition: iam.Conditions{"Bool": secureTransportCondition()},
},
{
Effect: iam.EffectAllow,
Expand All @@ -201,18 +298,66 @@ func controllersPolicy(accountID string) *iam.PolicyDocument {
},
Condition: iam.Conditions{
"StringLike": map[string]string{"iam:AWSServiceName": "elasticloadbalancing.amazonaws.com"},
"Bool": secureTransportCondition(),
},
},
{
Effect: iam.EffectAllow,
Resource: iam.Resources{"*"},
Action: iam.Actions{
"ec2:CreateTags",
randomvariable marked this conversation as resolved.
Show resolved Hide resolved
"ec2:RunInstances",
"elasticloadbalancing:CreateLoadBalancer",
},
Condition: *tagRequestForNewObjectConditions(),
},
{
Effect: iam.EffectAllow,
Resource: iam.Resources{"*"},
Action: iam.Actions{
"ec2:RunInstances",
},
Condition: *ec2RunInstanceConditions(accountID),
},
{
Effect: iam.EffectAllow,
Resource: iam.Resources{fmt.Sprintf(
"arn:aws:iam::%s:role/%s",
accountID,
iam.NewManagedName("*"),
)},
Resource: iam.Resources{
"arn:aws:ec2:::instance/*",
randomvariable marked this conversation as resolved.
Show resolved Hide resolved
},
Action: iam.Actions{
"ec2:CreateTags",
},
Condition: *tagRequestForExistingObjectConditions("ec2"),
},
{
Effect: iam.EffectAllow,
Resource: iam.Resources{"*"},
Action: iam.Actions{
"ec2:TerminateInstances",
"ec2:DescribeInstances",
},
Condition: *taggedObjectRequestCondition("ec2"),
},
{
Effect: iam.EffectAllow,
Resource: iam.Resources{"*"},
Action: iam.Actions{
"elasticloadbalancing:DeleteLoadBalancer",
"elasticloadbalancing:ConfigureHealthCheck",
"elasticloadbalancing:DescribeLoadBalancers",
"elasticloadbalancing:DescribeLoadBalancerAttributes",
"elasticloadbalancing:ModifyLoadBalancerAttributes",
"elasticloadbalancing:RegisterInstancesWithLoadBalancer",
},
Condition: *taggedObjectRequestCondition("elasticloadbalancing"),
},
{
Effect: iam.EffectAllow,
Resource: iam.Resources{resourceNamespace(accountID, "role")},
Action: iam.Actions{
"iam:PassRole",
},
Condition: iam.Conditions{"Bool": secureTransportCondition()},
},
},
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/cloud/aws/services/iam/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,5 +110,5 @@ func NewManagedName(prefix string) string {
// objects with Condition as per the AWS IAM policy schema as a work-around for
// https://github.com/awslabs/goformation/issues/157
func ProcessPolicyDocument(p string) string {
return strings.Replace(p, "IAMConditions", "Condition", 1)
return strings.Replace(p, "IAMConditions", "Condition", -1)
}