Skip to content

Commit

Permalink
Add support for well known policies with IRSA
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelbeaumont committed Jan 7, 2021
1 parent 7f6f072 commit 27ccd41
Show file tree
Hide file tree
Showing 12 changed files with 139 additions and 11 deletions.
36 changes: 36 additions & 0 deletions pkg/apis/eksctl.io/v1alpha5/assets/schema.json
Expand Up @@ -343,6 +343,9 @@
"description": "AWS tags for the service account",
"x-intellij-html-description": "AWS tags for the service account",
"default": "{}"
},
"wellKnownPolicies": {
"$ref": "#/definitions/WellKnownPolicies"
}
},
"preferredOrder": [
Expand All @@ -351,6 +354,7 @@
"labels",
"annotations",
"attachPolicyARNs",
"wellKnownPolicies",
"attachPolicy",
"permissionsBoundary",
"status",
Expand Down Expand Up @@ -1703,6 +1707,38 @@
"description": "defines the configuration for KMS encryption provider",
"x-intellij-html-description": "defines the configuration for KMS encryption provider"
},
"WellKnownPolicies": {
"properties": {
"autoScaler": {
"type": "boolean",
"default": "false"
},
"awsLoaderBalancer": {
"type": "boolean",
"default": "false"
},
"certManager": {
"type": "boolean",
"default": "false"
},
"externalDNS": {
"type": "boolean",
"default": "false"
},
"imageBuilder": {
"type": "boolean",
"default": "false"
}
},
"preferredOrder": [
"imageBuilder",
"autoScaler",
"awsLoaderBalancer",
"externalDNS",
"certManager"
],
"additionalProperties": false
},
"github.com|weaveworks|eksctl|pkg|utils|ipnet.IPNet": {
"type": "string",
"description": "an IP address in CIDR notation",
Expand Down
2 changes: 2 additions & 0 deletions pkg/apis/eksctl.io/v1alpha5/iam.go
Expand Up @@ -80,6 +80,8 @@ type ClusterIAMServiceAccount struct {
// +optional
AttachPolicyARNs []string `json:"attachPolicyARNs,omitempty"`

WellKnownPolicies WellKnownPolicies `json:"wellKnownPolicies,omitempty"`

// AttachPolicy holds a policy document to attach to this service account
// +optional
AttachPolicy InlineDocument `json:"attachPolicy,omitempty"`
Expand Down
6 changes: 3 additions & 3 deletions pkg/apis/eksctl.io/v1alpha5/schema.go

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions pkg/apis/eksctl.io/v1alpha5/validation.go
Expand Up @@ -53,8 +53,8 @@ func ValidateClusterConfig(cfg *ClusterConfig) error {
if ok, err := saNames.checkUnique("<namespace>/<name> of "+path, sa.NameString()); !ok {
return err
}
if len(sa.AttachPolicyARNs) == 0 && sa.AttachPolicy == nil {
return fmt.Errorf("%s.attachPolicyARNs or %s.attachPolicy must be set", path, path)
if !sa.WellKnownPolicies.HasPolicy() && len(sa.AttachPolicyARNs) == 0 && sa.AttachPolicy == nil {
return fmt.Errorf("%[1]s.wellKnownPolicies, %[1]s.attachPolicyARNs or %[1]s.attachPolicy must be set", path)
}
}

Expand Down
6 changes: 5 additions & 1 deletion pkg/apis/eksctl.io/v1alpha5/validation_test.go
Expand Up @@ -255,7 +255,11 @@ var _ = Describe("ClusterConfig validation", func() {
err = ValidateClusterConfig(cfg)
Expect(err).To(HaveOccurred())

Expect(err.Error()).To(HavePrefix("iam.serviceAccounts[1].attachPolicyARNs or iam.serviceAccounts[1].attachPolicy must be set"))
Expect(err.Error()).To(SatisfyAll(
ContainSubstring("iam.serviceAccounts[1]"),
ContainSubstring("attachPolicy"),
ContainSubstring("must be set"),
))
})

It("should fail when non-uniquely named iam.serviceAccounts are given", func() {
Expand Down
13 changes: 13 additions & 0 deletions pkg/apis/eksctl.io/v1alpha5/well_known_iam_policy.go
@@ -0,0 +1,13 @@
package v1alpha5

type WellKnownPolicies struct {
ImageBuilder bool `json:"imageBuilder,inline"`
AutoScaler bool `json:"autoScaler,inline"`
AWSLoadBalancer bool `json:"awsLoaderBalancer,inline"`
ExternalDNS bool `json:"externalDNS,inline"`
CertManager bool `json:"certManager,inline"`
}

func (p *WellKnownPolicies) HasPolicy() bool {
return p.ImageBuilder || p.AutoScaler || p.AWSLoadBalancer || p.ExternalDNS || p.CertManager
}
17 changes: 17 additions & 0 deletions pkg/apis/eksctl.io/v1alpha5/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 21 additions & 2 deletions pkg/cfn/builder/iam.go
Expand Up @@ -224,10 +224,27 @@ func (rs *IAMServiceAccountResourceSet) AddAllResources() error {
PermissionsBoundary: rs.spec.PermissionsBoundary,
RoleName: rs.spec.RoleName,
}
role.ManagedPolicyArns = append(role.ManagedPolicyArns, rs.spec.AttachPolicyARNs...)
for _, arn := range rs.spec.AttachPolicyARNs {
role.ManagedPolicyArns = append(role.ManagedPolicyArns, arn)
}

wellKnownPolicies := createWellKnownPolicies(rs.spec.WellKnownPolicies)

for _, p := range wellKnownPolicies {
for _, np := range p.NamedPolicies {
role.ManagedPolicyArns = append(role.ManagedPolicyArns, makePolicyARN(np))
}
}

roleRef := rs.template.NewResource("Role1", role)

for _, p := range wellKnownPolicies {
if p.Name != "" {
doc := cft.MakePolicyDocument(p.Statements...)
rs.template.AttachPolicy(p.Name, roleRef, doc)
}
}

// TODO: declare output collector automatically when all stack builders migrated to our template package
rs.template.Outputs["Role1"] = cft.Output{
Value: cft.MakeFnGetAttString("Role1.Arn"),
Expand Down Expand Up @@ -321,7 +338,9 @@ func (rs *IAMRoleResourceSet) AddAllResources() error {
role := &cft.IAMRole{
AssumeRolePolicyDocument: assumeRolePolicyDocument,
}
role.ManagedPolicyArns = append(role.ManagedPolicyArns, rs.attachPolicyARNs...)
for _, arn := range rs.attachPolicyARNs {
role.ManagedPolicyArns = append(role.ManagedPolicyArns, arn)
}

roleRef := rs.template.NewResource("Role1", role)

Expand Down
33 changes: 33 additions & 0 deletions pkg/cfn/builder/iam_helper.go
Expand Up @@ -18,6 +18,39 @@ type cfnTemplate interface {
newResource(name string, resource gfn.Resource) *gfnt.Value
}

type customPolicy struct {
Name string
Statements []cft.MapOfInterfaces
NamedPolicies []string
}

func createWellKnownPolicies(wellKnownPolicies api.WellKnownPolicies) []customPolicy {
var policies []customPolicy
if wellKnownPolicies.ImageBuilder {
policies = append(policies, customPolicy{NamedPolicies: []string{iamPolicyAmazonEC2ContainerRegistryPowerUser}})
} else if wellKnownPolicies.AutoScaler {
policies = append(policies, customPolicy{Name: "PolicyAutoScaling", Statements: autoScalerStatements()})
} else if wellKnownPolicies.AWSLoadBalancer {
policies = append(policies, customPolicy{Name: "PolicyAWSLoadBalancerController", Statements: loadBalancerControllerStatements()})
} else if wellKnownPolicies.ExternalDNS {
policies = append(policies,
customPolicy{Name: "PolicyExternalDNSChangeSet", Statements: changeSetStatements()},
customPolicy{Name: "PolicyExternalDNSHostedZones", Statements: externalDNSHostedZonesStatements()},
)
} else if wellKnownPolicies.CertManager {
policies = append(policies,
customPolicy{Name: "PolicyCertManagerChangeSet", Statements: changeSetStatements()},
customPolicy{Name: "PolicyCertManagerGetChange", Statements: certManagerGetChangeStatements()},
)
if wellKnownPolicies.ExternalDNS {
policies = append(policies, customPolicy{Name: "PolicyCertManagerHostedZones", Statements: certManagerHostedZonesStatements("route53:ListHostedZones", "route53:ListTagsForResource")})
} else {
policies = append(policies, customPolicy{Name: "PolicyCertManagerHostedZones", Statements: certManagerHostedZonesStatements()})
}
}
return policies
}

// createRole creates an IAM role with policies required for the worker nodes and addons
func createRole(cfnTemplate cfnTemplate, clusterIAMConfig *api.ClusterIAM, iamConfig *api.NodeGroupIAM, managed, enableSSM, forceAddCNIPolicy bool) error {
managedPolicyARNs, err := makeManagedPolicies(clusterIAMConfig, iamConfig, managed, enableSSM, forceAddCNIPolicy)
Expand Down
6 changes: 5 additions & 1 deletion pkg/cfn/builder/partition.go
Expand Up @@ -34,10 +34,14 @@ func MakeServiceRef(servicePrincipalName string) *gfnt.Value {
)
}

func makePolicyARN(policyName string) *gfnt.Value {
return gfnt.MakeFnSubString(fmt.Sprintf("arn:${%s}:iam::aws:policy/%s", gfnt.Partition, policyName))
}

func makePolicyARNs(policyNames ...string) []*gfnt.Value {
policyARNs := make([]*gfnt.Value, len(policyNames))
for i, policy := range policyNames {
policyARNs[i] = gfnt.MakeFnSubString(fmt.Sprintf("arn:${%s}:iam::aws:policy/%s", gfnt.Partition, policy))
policyARNs[i] = makePolicyARN(policy)
}
return policyARNs
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/cfn/template/api_test.go
Expand Up @@ -19,7 +19,7 @@ var _ = Describe("CloudFormation template", func() {

roleRef := t.NewResource("aRole", &IAMRole{
RoleName: "foo",
ManagedPolicyArns: []string{"abc"},
ManagedPolicyArns: []interface{}{"abc"},
})

t.Outputs["aRole"] = Output{
Expand Down
2 changes: 1 addition & 1 deletion pkg/cfn/template/iam.go
Expand Up @@ -25,7 +25,7 @@ type IAMRole struct {
Path string `json:",omitempty"`

AssumeRolePolicyDocument MapOfInterfaces `json:",omitempty"`
ManagedPolicyArns []string `json:",omitempty"`
ManagedPolicyArns []interface{} `json:",omitempty"`
PermissionsBoundary string `json:",omitempty"`
}

Expand Down

0 comments on commit 27ccd41

Please sign in to comment.