mirror of https://github.com/kubernetes/kops.git
Merge pull request #1170 from yissacharcw/extensible-iam-roles
Add support for extensible IAM permissions
This commit is contained in:
commit
165ead4fac
|
|
@ -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
|
||||||
|
```
|
||||||
|
|
@ -135,6 +135,9 @@ type ClusterSpec struct {
|
||||||
// missing: default policy (currently OS security upgrades that do not require a reboot)
|
// missing: default policy (currently OS security upgrades that do not require a reboot)
|
||||||
UpdatePolicy *string `json:"updatePolicy,omitempty"`
|
UpdatePolicy *string `json:"updatePolicy,omitempty"`
|
||||||
|
|
||||||
|
// Additional policies to add for roles
|
||||||
|
AdditionalPolicies *map[string]string `json:"additionalPolicies,omitempty"`
|
||||||
|
|
||||||
//HairpinMode string `json:",omitempty"`
|
//HairpinMode string `json:",omitempty"`
|
||||||
//
|
//
|
||||||
//OpencontrailTag string `json:",omitempty"`
|
//OpencontrailTag string `json:",omitempty"`
|
||||||
|
|
|
||||||
|
|
@ -134,6 +134,9 @@ type ClusterSpec struct {
|
||||||
// missing: default policy (currently OS security upgrades that do not require a reboot)
|
// missing: default policy (currently OS security upgrades that do not require a reboot)
|
||||||
UpdatePolicy *string `json:"updatePolicy,omitempty"`
|
UpdatePolicy *string `json:"updatePolicy,omitempty"`
|
||||||
|
|
||||||
|
// Additional policies to add for roles
|
||||||
|
AdditionalPolicies *map[string]string `json:"additionalPolicies,omitempty"`
|
||||||
|
|
||||||
//HairpinMode string `json:",omitempty"`
|
//HairpinMode string `json:",omitempty"`
|
||||||
//
|
//
|
||||||
//OpencontrailTag string `json:",omitempty"`
|
//OpencontrailTag string `json:",omitempty"`
|
||||||
|
|
|
||||||
|
|
@ -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
|
// WARNING: in.AdminAccess requires manual conversion: does not exist in peer-type
|
||||||
out.IsolateMasters = in.IsolateMasters
|
out.IsolateMasters = in.IsolateMasters
|
||||||
out.UpdatePolicy = in.UpdatePolicy
|
out.UpdatePolicy = in.UpdatePolicy
|
||||||
|
out.AdditionalPolicies = in.AdditionalPolicies
|
||||||
if in.EtcdClusters != nil {
|
if in.EtcdClusters != nil {
|
||||||
in, out := &in.EtcdClusters, &out.EtcdClusters
|
in, out := &in.EtcdClusters, &out.EtcdClusters
|
||||||
*out = make([]*kops.EtcdClusterSpec, len(*in))
|
*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
|
// WARNING: in.KubernetesAPIAccess requires manual conversion: does not exist in peer-type
|
||||||
out.IsolateMasters = in.IsolateMasters
|
out.IsolateMasters = in.IsolateMasters
|
||||||
out.UpdatePolicy = in.UpdatePolicy
|
out.UpdatePolicy = in.UpdatePolicy
|
||||||
|
out.AdditionalPolicies = in.AdditionalPolicies
|
||||||
if in.EtcdClusters != nil {
|
if in.EtcdClusters != nil {
|
||||||
in, out := &in.EtcdClusters, &out.EtcdClusters
|
in, out := &in.EtcdClusters, &out.EtcdClusters
|
||||||
*out = make([]*EtcdClusterSpec, len(*in))
|
*out = make([]*EtcdClusterSpec, len(*in))
|
||||||
|
|
|
||||||
|
|
@ -134,6 +134,9 @@ type ClusterSpec struct {
|
||||||
// missing: default policy (currently OS security upgrades that do not require a reboot)
|
// missing: default policy (currently OS security upgrades that do not require a reboot)
|
||||||
UpdatePolicy *string `json:"updatePolicy,omitempty"`
|
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 stores the configuration for each cluster
|
||||||
EtcdClusters []*EtcdClusterSpec `json:"etcdClusters,omitempty"`
|
EtcdClusters []*EtcdClusterSpec `json:"etcdClusters,omitempty"`
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,11 +17,14 @@ limitations under the License.
|
||||||
package model
|
package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"k8s.io/kops/pkg/apis/kops"
|
"k8s.io/kops/pkg/apis/kops"
|
||||||
"k8s.io/kops/pkg/model/iam"
|
"k8s.io/kops/pkg/model/iam"
|
||||||
"k8s.io/kops/upup/pkg/fi"
|
"k8s.io/kops/upup/pkg/fi"
|
||||||
"k8s.io/kops/upup/pkg/fi/cloudup/awstasks"
|
"k8s.io/kops/upup/pkg/fi/cloudup/awstasks"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -64,7 +67,7 @@ func (b *IAMModelBuilder) Build(c *fi.ModelBuilderContext) error {
|
||||||
|
|
||||||
var iamRole *awstasks.IAMRole
|
var iamRole *awstasks.IAMRole
|
||||||
{
|
{
|
||||||
rolePolicy, err := b.buildAWSIAMRolePolicy(role)
|
rolePolicy, err := b.buildAWSIAMRolePolicy()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -107,6 +110,63 @@ func (b *IAMModelBuilder) Build(c *fi.ModelBuilderContext) error {
|
||||||
}
|
}
|
||||||
c.AddTask(iamInstanceProfileRole)
|
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
|
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
|
// 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{
|
functions := template.FuncMap{
|
||||||
"IAMServiceEC2": func() string {
|
"IAMServiceEC2": func() string {
|
||||||
// IAMServiceEC2 returns the name of the IAM service for EC2 in the current region
|
// IAMServiceEC2 returns the name of the IAM service for EC2 in the current region
|
||||||
|
|
|
||||||
|
|
@ -1901,6 +1901,9 @@ func ListIAMRoles(cloud fi.Cloud, clusterName string) ([]*ResourceTracker, error
|
||||||
remove["masters."+clusterName] = true
|
remove["masters."+clusterName] = true
|
||||||
remove["nodes."+clusterName] = true
|
remove["nodes."+clusterName] = true
|
||||||
remove["bastions."+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
|
var roles []*iam.Role
|
||||||
// Find roles matching remove map
|
// Find roles matching remove map
|
||||||
|
|
@ -1979,6 +1982,9 @@ func ListIAMInstanceProfiles(cloud fi.Cloud, clusterName string) ([]*ResourceTra
|
||||||
remove["masters."+clusterName] = true
|
remove["masters."+clusterName] = true
|
||||||
remove["nodes."+clusterName] = true
|
remove["nodes."+clusterName] = true
|
||||||
remove["bastions."+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
|
var profiles []*iam.InstanceProfile
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue