Merge pull request #1170 from yissacharcw/extensible-iam-roles

Add support for extensible IAM permissions
This commit is contained in:
Justin Santa Barbara 2017-01-19 12:45:55 -05:00 committed by GitHub
commit 165ead4fac
7 changed files with 184 additions and 2 deletions

105
docs/iam_roles.md Normal file
View File

@ -0,0 +1,105 @@
# IAM Roles
Two IAM roles are created for the cluster: one for the masters, and one for the nodes.
The permissions are kept to the minimum required to setup and maintain the cluster.
Master permissions:
```
ec2:*
route53:*
elasticloadbalancing:*
ecr:GetAuthorizationToken
ecr:BatchCheckLayerAvailability
ecr:GetDownloadUrlForLayer
ecr:GetRepositoryPolicy
ecr:DescribeRepositories
ecr:ListImages
ecr:BatchGetImage
// The following permissions are only created if you are using etcd volumes with "encrypted: true" and a custom kmsKeyId.
// They are scoped to the kmsKeyId that you are using.
kms:Encrypt
kms:Decrypt
kms:ReEncrypt*
kms:GenerateDataKey*
kms:DescribeKey
kms:CreateGrant
kms:ListGrants
kms:RevokeGrant
```
Node permissions:
```
ec2:Describe*
route53:*
ecr:GetAuthorizationToken
ecr:BatchCheckLayerAvailability
ecr:GetDownloadUrlForLayer
ecr:GetRepositoryPolicy
ecr:DescribeRepositories
ecr:ListImages
ecr:BatchGetImage
```
## Adding Additional Policies
Sometimes you may need to extend the kops IAM roles to add additional policies. You can do this
through the `additionalPolicies` spec field. For instance, let's say you want
to add DynamoDB and Elasticsearch permissions to your nodes.
Edit your cluster via `kops edit cluster ${CLUSTER_NAME}` and add the following to the spec:
```
additionalPolicies:
node: |
[
{
"Effect": "Allow",
"Action": ["dynamodb:*"],
"Resource": ["*"]
},
{
"Effect": "Allow",
"Action": ["es:*"],
"Resource": ["*"]
}
]
```
After you're finished editing, your cluster spec should look something like this:
```
metadata:
creationTimestamp: "2016-06-27T14:23:34Z"
name: ${CLUSTER_NAME}
spec:
cloudProvider: aws
networkCIDR: 10.100.0.0/16
networkID: vpc-a80734c1
nonMasqueradeCIDR: 100.64.0.0/10
zones:
- cidr: 10.100.32.0/19
name: eu-central-1a
additionalPolicies:
node: |
[
{
"Effect": "Allow",
"Action": ["dynamodb:*"],
"Resource": ["*"]
},
{
"Effect": "Allow",
"Action": ["es:*"],
"Resource": ["*"]
}
]
```
Now you can update to have the changes take effect:
```
kops update cluster ${CLUSTER_NAME} --yes
```

View File

@ -135,6 +135,9 @@ type ClusterSpec struct {
// missing: default policy (currently OS security upgrades that do not require a reboot)
UpdatePolicy *string `json:"updatePolicy,omitempty"`
// Additional policies to add for roles
AdditionalPolicies *map[string]string `json:"additionalPolicies,omitempty"`
//HairpinMode string `json:",omitempty"`
//
//OpencontrailTag string `json:",omitempty"`

View File

@ -134,6 +134,9 @@ type ClusterSpec struct {
// missing: default policy (currently OS security upgrades that do not require a reboot)
UpdatePolicy *string `json:"updatePolicy,omitempty"`
// Additional policies to add for roles
AdditionalPolicies *map[string]string `json:"additionalPolicies,omitempty"`
//HairpinMode string `json:",omitempty"`
//
//OpencontrailTag string `json:",omitempty"`

View File

@ -313,6 +313,7 @@ func autoConvert_v1alpha1_ClusterSpec_To_kops_ClusterSpec(in *ClusterSpec, out *
// WARNING: in.AdminAccess requires manual conversion: does not exist in peer-type
out.IsolateMasters = in.IsolateMasters
out.UpdatePolicy = in.UpdatePolicy
out.AdditionalPolicies = in.AdditionalPolicies
if in.EtcdClusters != nil {
in, out := &in.EtcdClusters, &out.EtcdClusters
*out = make([]*kops.EtcdClusterSpec, len(*in))
@ -449,6 +450,7 @@ func autoConvert_kops_ClusterSpec_To_v1alpha1_ClusterSpec(in *kops.ClusterSpec,
// WARNING: in.KubernetesAPIAccess requires manual conversion: does not exist in peer-type
out.IsolateMasters = in.IsolateMasters
out.UpdatePolicy = in.UpdatePolicy
out.AdditionalPolicies = in.AdditionalPolicies
if in.EtcdClusters != nil {
in, out := &in.EtcdClusters, &out.EtcdClusters
*out = make([]*EtcdClusterSpec, len(*in))

View File

@ -134,6 +134,9 @@ type ClusterSpec struct {
// missing: default policy (currently OS security upgrades that do not require a reboot)
UpdatePolicy *string `json:"updatePolicy,omitempty"`
// Additional policies to add for roles
AdditionalPolicies *map[string]string `json:"additionalPolicies,omitempty"`
// EtcdClusters stores the configuration for each cluster
EtcdClusters []*EtcdClusterSpec `json:"etcdClusters,omitempty"`

View File

@ -17,11 +17,14 @@ limitations under the License.
package model
import (
"encoding/json"
"fmt"
"k8s.io/kops/pkg/apis/kops"
"k8s.io/kops/pkg/model/iam"
"k8s.io/kops/upup/pkg/fi"
"k8s.io/kops/upup/pkg/fi/cloudup/awstasks"
"reflect"
"strings"
"text/template"
)
@ -64,7 +67,7 @@ func (b *IAMModelBuilder) Build(c *fi.ModelBuilderContext) error {
var iamRole *awstasks.IAMRole
{
rolePolicy, err := b.buildAWSIAMRolePolicy(role)
rolePolicy, err := b.buildAWSIAMRolePolicy()
if err != nil {
return err
}
@ -107,6 +110,63 @@ func (b *IAMModelBuilder) Build(c *fi.ModelBuilderContext) error {
}
c.AddTask(iamInstanceProfileRole)
}
// Generate additional policies if needed, and attach to existing instance profile
if b.Cluster.Spec.AdditionalPolicies != nil {
roleAsString := reflect.ValueOf(role).String()
additionalPolicies := *(b.Cluster.Spec.AdditionalPolicies)
if additionalPolicy, ok := additionalPolicies[strings.ToLower(roleAsString)]; ok {
roleName := "additional." + name
var iamRole *awstasks.IAMRole
{
rolePolicy, err := b.buildAWSIAMRolePolicy()
if err != nil {
return err
}
iamRole = &awstasks.IAMRole{
Name: s(roleName),
RolePolicyDocument: fi.WrapResource(rolePolicy),
}
c.AddTask(iamRole)
}
{
p := &iam.IAMPolicy{
Version: iam.IAMPolicyDefaultVersion,
}
statements := make([]*iam.IAMStatement, 0)
json.Unmarshal([]byte(additionalPolicy), &statements)
p.Statement = append(p.Statement, statements...)
policy, err := p.AsJSON()
if err != nil {
return fmt.Errorf("error building IAM policy: %v", err)
}
t := &awstasks.IAMRolePolicy{
Name: s(roleName),
Role: iamRole,
PolicyDocument: fi.WrapResource(fi.NewStringResource(policy)),
}
c.AddTask(t)
}
{
iamInstanceProfileRole := &awstasks.IAMInstanceProfileRole{
Name: s(roleName),
InstanceProfile: iamInstanceProfile,
Role: iamRole,
}
c.AddTask(iamInstanceProfileRole)
}
}
}
}
return nil
@ -132,7 +192,7 @@ func (b *IAMModelBuilder) buildAWSIAMPolicy(role kops.InstanceGroupRole) (string
}
// buildAWSIAMRolePolicy produces the AWS IAM role policy for the given role
func (b *IAMModelBuilder) buildAWSIAMRolePolicy(role kops.InstanceGroupRole) (fi.Resource, error) {
func (b *IAMModelBuilder) buildAWSIAMRolePolicy() (fi.Resource, error) {
functions := template.FuncMap{
"IAMServiceEC2": func() string {
// IAMServiceEC2 returns the name of the IAM service for EC2 in the current region

View File

@ -1901,6 +1901,9 @@ func ListIAMRoles(cloud fi.Cloud, clusterName string) ([]*ResourceTracker, error
remove["masters."+clusterName] = true
remove["nodes."+clusterName] = true
remove["bastions."+clusterName] = true
remove["additional.nodes."+clusterName] = true
remove["additional.masters."+clusterName] = true
remove["additional.bastions."+clusterName] = true
var roles []*iam.Role
// Find roles matching remove map
@ -1979,6 +1982,9 @@ func ListIAMInstanceProfiles(cloud fi.Cloud, clusterName string) ([]*ResourceTra
remove["masters."+clusterName] = true
remove["nodes."+clusterName] = true
remove["bastions."+clusterName] = true
remove["additional.nodes."+clusterName] = true
remove["additional.masters."+clusterName] = true
remove["additional.bastions."+clusterName] = true
var profiles []*iam.InstanceProfile